2021.02.15 北师大寒假新生训练

2021.02.15 北师大寒假新生训练

Candies

  • 题意: x + 2 x + 4 x + ⋯ + 2 k − 1 x = n x+2x+4x+⋯+2^{k−1}x=n x+2x+4x++2k1x=n. It is guaranteed that at least one solution exists. Note that k > 1 k>1 k>1.
  • 解法:暴力搜索
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;

int main() {
    
    
	int T;
	scanf("%d", &T);
	while (T--) {
    
    
		ll N;
		scanf("%lld", &N);
		ll k = 2;
		while (N % ((1LL << k) - 1) != 0) k++;
		printf("%lld\n", N / ((1LL << k) - 1));
	}
	return 0;
}

Balanced Array

  • 题意:
  1. a的前 n/2 个元素是偶数
  2. a的后 n/2 个元素是奇数
  3. a的所有元素都是互不相同的正整数
  4. 前半部分的和等于后半部分的和
  • 解法:题目保证 n 是偶数。这样构造:2 4 6 8 … 1 3 5 … x
  • 找到一个x,使得 2 + 4 + 6 + … = 1 + 3 + 5 + … + x
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 200010;
int ans[maxn];
typedef long long ll;
int main() {
    
    
	int T;
	scanf("%d", &T);
	while (T--) {
    
    
		int N;
		scanf("%d", &N);
		if (N / 2 % 2 == 1) {
    
    
			printf("NO\n");
			continue;
		}
		printf("YES\n");
		ll res = 0;
		for (int i = 1; i <= N / 2; i++) {
    
    
			ans[i] = 2 * i;
			res += ans[i];
		}
		for (int i = N / 2 + 1; i <= N - 1; i++) {
    
    
			ans[i] = 2 * (i - N / 2) - 1;
			res -= ans[i];
		}
		ans[N] = res;
		for (int i = 1; i <= N; i++) {
    
    
			printf("%d%c", ans[i], i == N ? '\n' : ' ');
		}
	}
	return 0;
}

Alternating Subsequence

题意:找到最长的+ - + - + - + - + -(+ -交替即可)的序列,求最长序列的最大值。
思路:把序列切成 + - + -… 这样一块儿一块儿的,然后找每块儿的最大值。

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;

int main() {
    
    
	int T;
	scanf("%d", &T);
	while (T--) {
    
    
		int N;
		scanf("%lld", &N);
		ll last = 0, x, ans = 0;
		for (int i = 0; i < N; i++) {
    
    
			scanf("%lld", &x);
			if (!last) {
    
    
				last = x;
				continue;
			}
			if (last * x > 0) {
    
    
				last = max(last, x);
			}
			else {
    
    
				ans += last;
				last = x;
			}
		}
		ans += last;
		cout << ans << endl;
	}
	return 0;
}

Constant Palindrome Sum

  • 题意:给定一个长度为 n 的数列,n 为偶数,保证每个元素在 [ 1 , k ] 之间。每次操作可以把某个位置的数字变成 [ 1 , k ] 内的任意数字。要求让这个数列满足:对于所有的 i ∈ [ 1 , n/2 ],a[i] + a[n-i+1] 是一个定值。问最少的操作次数。
  • 思路:我们知道了 a i a_i ai a n − i + 1 a_{n-i+1} ani+1 的值之后,就可以知道他们的和sum,变为 [2,2k]的范围的某一个数需要的操作次数,具体来说,就是令 s u m = a i + a n − i + 1 , L = m i n ( a i , a n − i + 1 ) + 1 , R = m a x ( a i , a n − i + 1 ) + k sum = a_i+a_{n-i+1},L=min(a_i, a_{n-i+1})+1,R=max(a_i, a_{n-i+1})+k sum=ai+ani+1,L=min(ai,ani+1)+1,R=max(ai,ani+1)+k
  • 需要的操作次数
  1. [ 2 , L − 1 ] [2,L-1] [2,L1]: 2
  2. [ L , s u m − 1 ] [L, sum-1] [L,sum1]: 1
  3. [ s u m , s u m ] [sum, sum] [sum,sum]: 0
  4. [ s u m + 1 , R ] [sum+1, R] [sum+1,R]: 1
  5. [ R + 1 , 2 K ] [R+1, 2K] [R+1,2K]: 2
  • 然后,用一个差分数组记录这些数字就好,找到一个最小值。
  • 这个题实际上是把某个取值范围映射到一个区间上。
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 400010;

int f[maxn], a[maxn];

void add(int L, int R, int c) {
    
    
	if (L > R) return;
	f[L] += c;
	f[R + 1] -= c;
}

int main() {
    
    
	int T;
	scanf("%d", &T);
	while (T--) {
    
    
		int N, K;
		scanf("%d%d", &N, &K);
		//memset(f, 0, sizeof f);
		fill(f, f + 2 * K + 1, 0);
		for (int i = 1; i <= N; i++) {
    
    
			scanf("%d", &a[i]);
		}
		for (int i = 1; i <= N / 2; i++) {
    
    
			int L = min(a[i], a[N - i + 1]) + 1, R = max(a[i], a[N - i + 1]) + K,
				sum = a[i] + a[N - i + 1];
			add(2, L - 1, 2), add(L, sum - 1, 1);
			add(sum + 1, R, 1), add(R + 1, 2 * K, 2);
		}
		int ans = 1e9;
		for (int i = 2; i <= 2 * K; i++) {
    
    
			f[i] += f[i - 1];
			ans = min(f[i], ans);
		}
		cout << ans << endl;
	}
	return 0;
}

Weights Distributing

  • 图论(队友写的)
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> PII;
ll dis[3][200010];
ll price[500010];
ll e[1000010],ne[1000010],h[1000010],idx;
ll n,m;
void add(ll a,ll b)
{
    
    
    e[++idx]=b;
    ne[idx]=h[a];
    h[a]=idx;
}
void dijkstra(ll start,ll x)
{
    
    
    priority_queue<PII,vector<PII>,greater<PII> >q;
    memset(dis[x],0x3f,sizeof dis[x]);
    dis[x][start]=0;
    q.push({
    
    0,start});
    while(q.size())
    {
    
    
        PII t=q.top();
        q.pop();
        ll dist=t.first,u=t.second;
        for(ll i=h[u];i;i=ne[i])
        {
    
    
            ll v=e[i];
            if(dis[x][v]>dist+1)
            {
    
    
                dis[x][v]=dist+1;
                q.push({
    
    dist+1,v});
            }
        }
    }
}
void solve()
{
    
    
    ll A,B,C;
    scanf("%lld%lld%lld%lld%lld",&n,&m,&A,&B,&C);
    for(ll i=1;i<=n;i++)
    {
    
    
        h[i]=0;
    }
    idx=0;
    for(ll i=1;i<=m;i++)
        scanf("%lld",&price[i]);
    sort(price+1,price+1+m);
    for(ll i=1;i<=m;i++)
        price[i]+=price[i-1];
    for(ll i=1;i<=m;i++)
    {
    
    
        ll a,b;
        scanf("%lld%lld",&a,&b);
        add(a,b);
        add(b,a);
    }
    dijkstra(A,1);
    dijkstra(C,2);
    dijkstra(B,0);
    ll ans=5*1e15;
    for(ll i=1;i<=n;i++)
    {
    
    
        ll len1=dis[0][i],len2=dis[1][i],len3=dis[2][i];
        if(len1+len2+len3>m)
            continue;
        ans=min(ans,price[len1]+price[len1+len2+len3]);
    }
    printf("%lld\n",ans);
}
int main()
{
    
    
    int T;
    scanf("%d",&T);
    while(T--)
    {
    
    
        solve();
    }
    return 0;
}

Restore the Permutation by Sorted Segments

  • 题意,构造一个排列,给你一些segment,segment里面的数字在原排列中是挨在一块儿的,而且每个segment右区间下标 r 是从 2 到 N 的。
  • 只要确定第一个数字,这个排列就确定了。
  • 确定第一个数字后,找到size为2的集合,那么集合中另外一个数字就是所求排列的第二个数。此时,遍历所有集合,如果存在某个集合含第一个数不含第二个数,那么说明当前确定的第一个数是不成立的,break掉。
  • 如果成立,那么erase所有集合中的上述第一个数字。那么寻找排列第二个数字的过程和前面是一样的。
  • 思路不复杂,代码实现起来稍微复杂一些。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<unordered_set>
using namespace std;

const int maxn = 210;
typedef unordered_set<int> us;
us segs[maxn], nsegs[maxn];
int ans[maxn];

int main() {
    
    
	int T;
	scanf("%d", &T);
	while (T--) {
    
    
		memset(ans, 0, sizeof ans);
		for (int i = 0; i < maxn; i++) segs[i].clear();


		int N;
		scanf("%d", &N);
		
		for (int i = 1; i < N; i++) {
    
    
			int sz;
			scanf("%d", &sz);
			for (int j = 0; j < sz; j++) {
    
    
				int x;
				scanf("%d", &x);
				segs[i].insert(x);
			}
		}

		for (int i = 1; i <= N; i++) {
    
    
			ans[1] = i;
			bool ok = false;

			for (int j = 1; j < N; j++) {
    
    
				nsegs[j] = segs[j];
			}
			
			for (int j = 1; j < N; j++) {
    
    
				int m = 0;

				for (int k = 1; k < N; k++) {
    
    
					if (nsegs[k].size() == 2 && nsegs[k].count(ans[j])) {
    
    
						for (auto p : nsegs[k]) {
    
    
							if (p != ans[j]) m = p;
						}
					}
				}

				bool flag = true;

				//if (ans[1] == 3) printf("###\n");

				for (int k = 1; k < N; k++) {
    
    
					if (nsegs[k].size() > 1 && nsegs[k].count(ans[j]) && !nsegs[k].count(m)) {
    
    
						flag = false;
						break;
					}
					
				}
				if (!flag) break;

				//if (ans[1] == 3) printf("$$$\n");

				ans[j + 1] = m;
				for (int k = 1; k < N; k++) {
    
    
					nsegs[k].erase(ans[j]);
					//if (ans[1] == 3) printf("*** %d\n", nsegs[k].size());
				}
				if (j + 1 == N) {
    
    
					ok = true;
				}
			}
			if (ok) break;
		}
		for (int i = 1; i <= N; i++) {
    
    
			printf("%d%c", ans[i], i == N ? '\n' : ' ');
		}
	}
	return 0;
}

Bamboo Leaf Rhapsody

题意:找到离原点最近的点,求这个距离是多少。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;

int main() {
    
    
	int N;
	scanf("%d", &N);
	int res = 1e9;
	for (int i = 0; i < N; i++) {
    
    
		int x, y, z;
		scanf("%d%d%d", &x, &y, &z);
		res = min(res, x * x + y * y + z * z);
	}
	printf("%.3f\n", sqrt(res));
	return 0;
}

Cheat Sheet

  • 去重,然后按照字符串的长度排序,依次挑选即可。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<string>
#include<unordered_set>
#include<vector>
using namespace std;

bool cmp(const string& s1, const string& s2) {
    
    
	return (int)s1.size() < (int)s2.size();
}

unordered_set<string> strs;
vector<string> words;
int main() {
    
    
	int N, M;
	scanf("%d%d", &N, &M);
	for (int i = 0; i < M; i++) {
    
    
		string s;
		cin >> s;
		strs.insert(s);
	}
	for (auto p : strs) {
    
    
		words.push_back(p);
	}
	sort(words.begin(), words.end(), cmp);
	int ans = 0;
	for (auto p : words) {
    
    
		if (N >= (int)p.size()) {
    
    
			ans++;
			N -= p.size() + 1;
		}
	}
	cout << ans << endl;
	return 0;
}

A. Archmage

  • 题意:一个人,每秒使用技能消耗x魔法值,每秒都可以回复y点魔法值。问m秒可以使用几次技能?
  • x <= y,直接输出m
  • x > y,由于 x + y <= n,因此不存在爆掉问题,但是存在魔法不足的问题。题意是先使用技能再回复,因此总共有 ( m − 1 ) ∗ y + n (m-1)*y + n (m1)y+n 的魔法可以使用。
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
typedef long long ll;
int main() {
    
    
	int T;
	scanf("%d", &T);
	while (T--) {
    
    
		ll n, m, x, y;
		scanf("%lld%lld%lld%lld", &n, &m, &x, &y);
		printf("%lld\n", min(m, (n + (m - 1) * y) / x));
	}
	return 0;
}

Hay Mower

  • 由于k比R和C大得多,所以肯定有很多重复的。只需要保存最大的 t i t_i ti 就好。
  • 求出来第 ( i , j ) (i,j) (i,j)个格点最后一次修改的时间,即 max(r[i], c[j])。
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod = 998244353;
const int maxn = 510;
ll r[maxn], c[maxn];
ll a[maxn][maxn];

ll mul(ll x, ll y) {
    
    
	return (x % mod) * (y % mod) % mod;
}

int main() {
    
    
	int N, M, K;
	scanf("%d%d%d", &N, &M, &K);
	for (int i = 1; i <= N; i++) {
    
    
		for (int j = 1; j <= M; j++) {
    
    
			scanf("%lld", &a[i][j]);
		}
	}
	char op[5];
	ll x, t;
	for (int i = 0; i < K; i++) {
    
    
		scanf("%s%lld%lld", op, &x, &t);
		if (op[0] == 'r') r[x] = max(t, r[x]);
		else c[x] = max(t, c[x]);
	}
	ll ans = 0;
	for (int i = 1; i <= N; i++) {
    
    
		for (int j = 1; j <= M; j++) {
    
    
			t = max(r[i], c[j]);
			ans = (ans + mul(a[i][j], t)) % mod;
		}
	}
	cout << ans << endl;
	return 0;
}

Disaster Recovery

  • 图论(队友写的)
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
typedef struct
{
    
    
    ll u,v;
}Edge;
Edge edge[200010];
ll n,m,du[100010],p[100010];
ll fib[100010];
ll ancester(ll x)
{
    
    
    if(x==p[x])
        return x;
    return p[x]=ancester(p[x]);
}
int cmp(Edge a,Edge b)
{
    
    
    if(a.u==b.u)
        return a.v<b.v;
    return a.u<b.u;
}
int main()
{
    
    
    scanf("%lld%lld",&n,&m);
    for(ll i=1;i<=m;i++)
    {
    
    
        ll a,b;
        scanf("%lld%lld",&a,&b);
        edge[i].u=max(a,b);
        edge[i].v=min(a,b);
    }
    for(ll i=1;i<=n;i++)
        p[i]=i;
    sort(edge+1,edge+1+m,cmp);
    for(ll i=1;i<=m;i++)
    {
    
    
        ll u=edge[i].u,v=edge[i].v;
        ll fu=ancester(u),fv=ancester(v);
        if(fu!=fv)
        {
    
    
            p[fu]=fv;
            du[u]++;
            du[v]++;
        }
    }
    ll ans=0;
    for(ll i=1;i<=n;i++)
        ans=max(ans,du[i]);
    printf("%lld",ans);
    return 0;
}

Lottery Tickets

  • 为能组成的4的倍数最大是多少。
  • 发现,后两位是4的倍数,前面不管怎么凑都是4的倍数,按题意就从大往小以此排列,最后不要忘记去掉前导零。若找不到后两位,就看一位数字行不行(0,4,8)。要是都不行的话就输出-1.
  • 细节有点多,一开始容易写错
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;

int c[10];
vector<int> ans;
int main() {
    
    
	int T;
	scanf("%d", &T);
	while (T--) {
    
    
		ans.clear();
		for (int i = 0; i <= 9; i++) scanf("%d", &c[i]);
		int m0 = 10, m1 = 10;
		//最后能否找到两位数,是4的倍数
		for (int i = 0; i <= 96; i += 4) {
    
    
			int d0 = i % 10, d1 = i / 10;
			int mmax = max(m0, m1), mmin = min(m0, m1);
			int dmax = max(d0, d1), dmin = min(d0, d1);

			if ((d0 == d1 && c[d0] >= 2) || (d0 != d1 && c[d0] && c[d1])) {
    
    
				if (mmax > dmax || mmax == dmax && mmin > dmin) {
    
    
					m1 = d1, m0 = d0;
					//printf("### %d %d\n", m0, m1);
				}
				else if (mmax == dmax && mmin == dmin) {
    
    
					m1 = dmax, m0 = dmin;
				}
			}
		}

		if (m0 >= 10 && m1 >= 10) {
    
    
			if (c[8]) printf("8\n");
			else if (c[4]) printf("4\n");
			else if (c[0]) printf("0\n");
			else printf("-1\n");
			continue;
		}
		
		c[m0]--, c[m1]--;
		ans.push_back(m0), ans.push_back(m1);
		//printf("*** %d %d\n", m0, m1);
		for (int i = 0; i <= 9; i++) {
    
    
			for (int j = 0; j < c[i]; j++) {
    
    
				ans.push_back(i);
			}
		}
		while (ans.size() > 1 && ans.back() == 0) ans.pop_back();
		for (int i = ans.size() - 1; i >= 0; i--) {
    
    
			printf("%d", ans[i]);
		}
		printf("\n");
	}
}

Gentle Jena

  • 这道题难点就在 A i A_i Ai 的计算上,其他的都很简单。
  • 每次计算 A i A_i Ai 时,相当于 A i − 1 + ∑ j = 1 i f ( j , i )   %   m o d A_{i-1}+\sum\limits_{j=1}^if(j, i)\ \%\ mod Ai1+j=1if(j,i) % mod
  • 用单调栈可以写,为什么呢?我们这么来考虑单调栈这样一个性质。在原数组中,若 i < j , a i < a j i<j, a_i < a_j i<j,ai<aj,那么若 a i a_i ai 在栈内,则 a j a_j aj 必然在栈内。那么对于那个题,如果新来一个数字 b n b_n bn,把这个 b n b_n bn 开始往栈里面推,如果推到了栈内元素 id 的后面,那么在 [id+1, n] 这个区间内,最小值都是 b n b_n bn,结果为 b n ∗ ( n − i d ) b_n*(n-id) bn(nid),在 [1, id],结果为 s u m [ i d ] sum[id] sum[id]. s u m [ i ] = [ f ( 1 , i ) + f ( 2 , i ) + . . . + f ( i , i ) ]   %   m o d sum[i] = \Bigl[f(1,i)+f(2,i)+...+f(i,i)\Bigl]\ \%\ mod sum[i]=[f(1,i)+f(2,i)+...+f(i,i)] % mod
#include<cstring>
#include<algorithm>
#include<iostream>

typedef long long ll;
using namespace std;
const int maxn = 10000010;
const ll mod = 998244353;
int stk[maxn];

ll A[maxn], b[maxn], sum[maxn]; //sum[i] 维护的是 f(1,i)+f(2,i)+...+f(i,i).

int main() {
    
    
	ll n, p, x, y, z, b1;
	cin >> n >> p >> x >> y >> z >> b1;
	b[1] = A[1] = sum[1] = b1;
	int tt = 0;
	stk[++tt] = 1;
	for (int i = 2; i <= n; i++) {
    
    
		b[i] = (x * A[i - 1] % p + y * b[i - 1] % p + z) % p;
		while (tt && b[stk[tt]] >= b[i]) tt--;
		sum[i] = (b[i] * (ll)(i - stk[tt]) % mod + sum[stk[tt]] % mod);
		A[i] = (A[i - 1] + sum[i]) % mod;
		stk[++tt] = i;
	}
	ll ans = 0;
	for (int i = 1; i <= n; i++) {
    
    
		ans ^= A[i];
		//printf("*** %lld\n", A[i]);
		//printf("### %lld\n", b[i]);
	}
	cout << ans << endl;
}

猜你喜欢

转载自blog.csdn.net/qq_45812711/article/details/113806499