Codeforces Round #726 div.2 A-F题解

视频讲解:BV1Hy4y1M74u

A. Arithmetic Array

题目大意

给定一个包含 n n n 个整数的数组 a a a ,求最少需要向数组 a a a 中添加多少个非负整数,使得数组的算术平均数恰好等于 1 1 1

题解

s u m = ∑ i = 1 n a i sum=\sum_{i=1}^{n}{a_i} sum=i=1nai ,有以下两种情况:

  1. s u m < n sum < n sum<n ,则再添加一个数值为 n − s u m + 1 n-sum+1 nsum+1 的元素,即可成立;
  2. s u m ≥ n sum \geq n sumn ,则再添加 s u m − n sum-n sumn 0 0 0 元素,即可成立;

参考代码

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;

int main()
{
	int T,n,x,sum,i;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		sum=0;
		for(i=1;i<=n;i++)
		{
			scanf("%d",&x);
			sum+=x;
		}
		if(sum-n<0)
			printf("1\n");
		else
			printf("%d\n",sum-n);
	}
}

B. Bad Boy

题目大意

在一个 n n n m m m 列的网格上,初始在 ( i , j ) (i,j) (i,j) 位置,每次可以向上下左右四个方向之一移动一格。
现在在网格上任意位置放置两个悠悠球,求如何放置这两个悠悠球,使得从 ( i , j ) (i,j) (i,j) 出发,捡到两个悠悠球,再回到初始位置的路径最长。

题解

两个悠悠球在对角的两个角落,即 ( 1 , 1 ) (1,1) (1,1) ( n , m ) (n,m) (n,m) 位置,或 ( 1 , m ) (1,m) (1,m) ( n , 1 ) (n,1) (n,1) 位置,总路程最长,均为 2 n + 2 m 2n+2m 2n+2m 。输出任意解即可。

参考代码

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;

int main()
{
	int T,n,m,i,j;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d%d%d",&n,&m,&i,&j);
		printf("%d %d %d %d\n",1,1,n,m);
	}
}

C. Challenging Cliffs

题目大意

给定 n n n 座山,每座山的高度为 h i h_i hi 。将其按任意顺序排列在一条直线上,从左到右编号为 1 1 1 n n n
∣ h 1 − h n ∣ |h_1-h_n| h1hn 最小时,满足条件 h i < h i + 1 ( 1 ≤ i < n ) h_i < h_{i+1}(1 \leq i < n) hi<hi+1(1i<n) i i i 最多时的排列方案。

题解

h i h_i hi 递增排序后,必定有最多的 i i i 满足 h i < h i + 1 ( 1 ≤ i < n ) h_i < h_{i+1}(1 \leq i < n) hi<hi+1(1i<n)
接下来考虑使得 ∣ h 1 − h n ∣ |h_1-h_n| h1hn 最小,找到最小的 h j + 1 − h j h_{j+1}-h_j hj+1hj ,将第 j + 1 j+1 j+1 n n n 座山全部移到第 1 1 1 座山前面即可。
注意如果只有两座山时,由于代码写法不同,可能需要特殊考虑。

参考代码

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;

const int MAXN=200200;
int h[MAXN];

int main()
{
	int T,n,i,mn,id;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		for(i=1;i<=n;i++)
			scanf("%d",&h[i]);
		sort(h+1,h+n+1);
		mn=h[n]-h[1];
		id=n;
		for(i=1;i<n;i++)
		{
			if(h[i+1]-h[i]<mn)
			{
				mn=h[i+1]-h[i];
				id=i;
			}
		}
		for(i=id+1;i<=n;i++)
			printf("%d ",h[i]);
		for(i=1;i<=id;i++)
			printf("%d ",h[i]);
		puts("");
	}
}

D. Deleting Divisors

题目大意

Alice 和 Bob 进行博弈游戏。
初始给定一个正整数 n n n ,每轮将其减去 x x x x x x n n n 的因数且不为 1 1 1 n n n ,无法操作时失败,Alice先手。
双方均采用最优策略,求谁获胜。

题解

n n n 有以下三种状态:

  1. 奇数;
  2. 不为 2 2 2 的整数次幂的偶数;
  3. 2 2 2 的整数次幂;

对于奇数,其只能变成不为 2 2 2 的整数次幂的偶数。证明:

  • n = x y n=xy n=xy ,减去一个合法因数 x x x 后, n − x = x ( y − 1 ) n-x=x(y-1) nx=x(y1) ,其中 x x x 为奇数,因此 n − x = x ( y − 1 ) n-x=x(y-1) nx=x(y1) 必定是不为 2 2 2 的整数次幂的偶数。

对于不为 2 2 2 的整数次幂的偶数,有以下两种变化方案:

  1. 减去一个奇因数 x 1 x1 x1 ,变成奇数;
  2. 减去一个偶因数 x 2 x2 x2 ,变为偶数;

若执行方案1,减去一个奇因数 x 1 x1 x1 ,变成奇数后,则对手只能将其再减去一个奇数变回不为 2 2 2 的整数次幂的偶数,或直接面对一个奇素数的必败状态。
因此不为 2 2 2 的整数次幂的偶数是必胜状态,奇数是必败状态。

对于 2 2 2 的整数次幂 2 k 2^k 2k,有以下两种变化方案:

  1. 减去 2 p ( 1 ≤ p ≤ k − 2 ) 2^p(1 \leq p \leq k-2) 2p(1pk2) ,变为不为 2 2 2 的整数次幂的偶数 2 p ∗ ( 2 k − p − 1 ) 2^p*(2^{k-p}-1) 2p(2kp1) ,即必胜状态;
  2. 减去 2 k − 1 2^{k-1} 2k1 ,变为 2 2 2 的整数次幂 2 k − 1 2^{k-1} 2k1

由于方案1等于将必胜状态交给对手,因此不会选择方案1。
对于方案2,由于 k = 1 k=1 k=1 2 1 2^1 21 为必败状态,因此若 k k k 为偶数是必胜状态, k k k 为奇数是必败状态。

参考代码

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;

int main()
{
	int T,n,num;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		if(n%2)
		{
			printf("Bob\n");
			continue;
		}
		num=0;
		while(n%2==0)
		{
			n/=2;
			num++;
		}
		if(n>1||num%2==0)
			printf("Alice\n");
		else
			printf("Bob\n");
	}
}

E1+E2. Erase and Extend

题目大意

给定一个长度为 n n n 的字符串 s s s ,可以对其进行任意次数和顺序的下述两种操作:

  1. 删除末尾的一个字符;
  2. 拷贝字符串,即 s = s + s s=s+s s=s+s

求可以变成的字典序最小的长度为 k k k 的字符串。

对于 Easy Version, 1 ≤ n , k ≤ 5 ⋅ 1 0 3 1 \leq n,k \leq 5 \cdot 10^3 1n,k5103
对于 Hard Version, 1 ≤ n , k ≤ 5 ⋅ 1 0 5 1 \leq n,k \leq 5 \cdot 10^5 1n,k5105

题解

答案必定是字符串 s s s 的某一前缀 A A A 的若干次重复结果 A A A . . . AAA... AAA... ,即先删除若干次,再拷贝若干次,再删除若干次。、
可以用反证法证明:

  • 假设最优解的拷贝过程中存在若干次删除操作,即存在 A . . . B . . . A . . . A...B...A... A...B...A... A A A . . . AAA... AAA... 更优,那么 B B B . . . BBB... BBB... 是比 A . . . B . . . A . . . A...B...A... A...B...A... 更优的结果,存在矛盾。

因此对于 Easy Version,直接枚举所有前缀构造字符串,找出最优解即可。复杂度为 O ( n k ) O(nk) O(nk)

对于 Hard Version,可以记录历史最优前缀的长度为 p p p ,从左到右遍历字符 s i s_i si ,有以下几种可能:

  • s i > s i % p s_i > s_{i\%p} si>si%p ,则不存在更优的前缀,其长度大于 i i i 。因此后续不用再考虑;
  • s i = s i % p s_i = s_{i\%p} si=si%p ,则表示 s [ 0 , i ] s[0,i] s[0,i] 可以视为 s [ 0 , p − 1 ] s[0,p-1] s[0,p1] 的若干次重复,前缀 s [ 0 , i ] s[0,i] s[0,i] 不会比前缀 s [ 0 , p − 1 ] s[0,p-1] s[0,p1] 更优,跳过;
    • 证明:假设前缀 s [ 0 , i ] = A A . . A B s[0,i]=AA..AB s[0,i]=AA..AB 比前缀 s [ 0 , p − 1 ] = A s[0,p-1]=A s[0,p1]=A 更优,则前缀 s [ 0 , i % p ] = B s[0,i\%p]=B s[0,i%p]=B 是比前缀 s [ 0 , p − 1 ] = A s[0,p-1]=A s[0,p1]=A 更优的解,存在矛盾。
  • s i < s i % p s_i < s_{i\%p} si<si%p ,则表示 s [ 0 , i ] s[0,i] s[0,i] s [ 0 , p − 1 ] s[0,p-1] s[0,p1] 的若干次重复的字典序更小,是更优解,需要将 p p p 更新为 i + 1 i+1 i+1

这样就可以在 O ( n ) O(n) O(n) 复杂度内找出最优解。

参考代码

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;

const int MAXN=500500;
char s[MAXN];

int main()
{
	int n,k,p,i;
	scanf("%d%d",&n,&k);
	scanf("%s",&s);
	p=1;
	for(i=1;i<n;i++){
		if(s[i%p]<s[i])
			break;
		if(s[i%p]>s[i])
			p=i+1;
	}
	for(i=0;i<k;i++)
		printf("%c",s[i%p]);
	puts("");
}

F. Figure Fixing

题目大意

给定一张包含 n n n 个点 m m m 条边的无向连通图,第 i i i 个节点有当前权值 v i v_i vi 和目标权值 t i t_i ti 两种属性。
每次可以选择一条边 ( i , j ) (i,j) (i,j) ,将 v i v_i vi v j v_j vj 都增加任意整数 k k k 。可以执行任意次上述操作。
判断能否将所有节点的 v i v_i vi 都变为 t i t_i ti

题解

由于每次操作,对于整张图的权值总和会增加 2 k 2k 2k ,因此若 ∑ t i − v i \sum{t_i-v_i} tivi 不为偶数,则无解。

如果图是二分图,考虑将每个节点染为红色或蓝色,每个红色节点的相邻节点都是蓝色节点,每个蓝色节点的相邻颜色都是红色节点。
每次操作等价于将所有红色节点的 s u m 1 = ∑ t i − v i sum1=\sum{t_i-v_i} sum1=tivi 和蓝色节点的 s u m 2 = ∑ t i − v i sum2=\sum{t_i-v_i} sum2=tivi 同时增加 k k k 。因此若初始 s u m 1 ≠ s u m 2 sum1 \neq sum2 sum1=sum2 ,也无解。反之有解。

若图不是二分图,则至少存在一条连接两个同色节点的边,即可以改变一种颜色的 s u m sum sum 值时不改变另一种颜色的 s u m sum sum 值,因此总是有解的。

参考代码

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;

const int MAXN=200200;
int t[MAXN],v[MAXN],col[MAXN];
ll sum[5];
vector<int> e[MAXN];

bool check()
{
	queue<int> q;
	q.push(1);
	col[1]=1;
	sum[1]=sum[2]=0;
	while(!q.empty())
	{
		int now=q.front();
		q.pop();
		sum[col[now]]+=t[now]-v[now];
		for(int i=0;i<e[now].size();i++)
		{
			int nxt=e[now][i];
			if(col[nxt]==col[now])
				return true;
			if(col[nxt])
				continue;
			col[nxt]=3-col[now];
			q.push(nxt);
		}
	}
	return sum[1]==sum[2];
}

int main()
{
	int T,n,m,i,a,b;
	ll all;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&m);
		for(i=1;i<=n;i++)
			scanf("%d",&v[i]);
		for(i=1;i<=n;i++)
			scanf("%d",&t[i]);
		for(i=1;i<=n;i++)
		{
			e[i].clear();
			col[i]=0;
		}
		while(m--)
		{
			scanf("%d%d",&a,&b);
			e[a].push_back(b);
			e[b].push_back(a);
		}
		all=0;
		for(i=1;i<=n;i++)
			all+=t[i]-v[i];
		if(all%2||!check())
			printf("NO\n");
		else
			printf("YES\n");
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值