Codeforces Round #730 (Div. 2)题解+补题

A. Exciting Bets

一、题目链接

https://codeforces.ml/contest/1543/problem/A

二、题目大意

t t t组数据,每组数据包含一对 a , b a,b a,b。你可以进行任意多次操作,每次操作可以将 a , b a,b a,b同时加一或减一。设最后得到的两个数分别是 x , y x,y x,y,题目希望输出两个值,一个是最大的 g c d ( x , y ) gcd(x,y) gcd(x,y),一个是得到最大的 g c d ( x , y ) gcd(x,y) gcd(x,y)需要的最小操作次数。

数据范围: 1 ≤ t ≤ 5 × 1 0 3 1≤t≤5\times10^3 1t5×103 0 ≤ a , b ≤ 1 0 18 0≤a,b≤10^{18} 0a,b1018

三、题目分析

  • 首先我们假设 a < b a<b a<b,因为要得到最小操作次数,所以显然一直加一和一直减一操作次数最小。设最小操作次数为 x x x
  • 由欧几里得定律可知, g c d ( a + x , b + x ) gcd(a+x,b+x) gcd(a+x,b+x)的值与 g c d ( b + x , b − a ) gcd(b+x,b-a) gcd(b+x,ba)的结果相同,那么很显然,答案所需的第一个值一定是 b − a b-a ba
  • 然后考虑如何让操作次数最小,显然此处有两种答案,一种是 x x x为负,使得 b + x b+x b+x b − a b-a ba的倍数,一种是 x x x为正,使得 b + x b+x b+x b − a b-a ba的倍数。分别求出两种方案的 ∣ x ∣ \left|x\right| x,然后选择最小值输出即可。
  • 最后注意一下 a = b a=b a=b和a或b出现0的特殊情况即可。

四、正解程序

#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cstdio>

using namespace std;
typedef long long ll;
ll T;
int main()
{
	scanf("%lld",&T);
	while(T--)
	{
		ll a,b;
		scanf("%lld%lld",&a,&b);
		if(a>b)//首先保证a<b
			swap(a,b);
		if(a==b)//处理特殊情况
			printf("0 0\n");
		else
		{
			ll t1=b-a,t2=0,t3=0;
			if(b%t1==0)
				t2=0;
			else//分别算出x为正和为负两种情况的操作次数
			{
				t2=(b/t1+1)*t1-b;
				t3=b-b/t1*t1;
			}
			printf("%lld %lld\n",t1,min(t2,t3));
		}
	}
	
	return 0;
}

B. Customising the Track

一、题目链接

https://codeforces.ml/contest/1543/problem/B

二、题目大意

t t t组数据,每组数据包含一个长度为 n n n的序列 a a a。你可以对序列 a a a中的值进行任意操作,但不能使其小于等于 0 0 0,且总和不变,要求使得最后 ∑ i = 1 n ∑ j = i + 1 n ∣ a [ i ] − a [ j ] ∣ \sum_{i=1}^{n}\sum_{j=i+1}^{n}\left|a[i]-a[j]\right| i=1nj=i+1na[i]a[j]的值尽可能小。输出最后的最小值。

数据范围: 1 ≤ t ≤ 1 0 4 1≤t≤10^4 1t104 1 ≤ n ≤ 2 × 1 0 5 1≤n≤2\times10^5 1n2×105 0 ≤ a [ i ] ≤ 1 0 9 0≤a[i]≤10^{9} 0a[i]109

三、题目分析

  • 一个显然的结论是,如果能将序列 a a a平分成 n n n块,那么答案一定为最小值0。
  • 所以我们可以把 ⌊ s u m / n ⌋ \lfloor sum/n\rfloor sum/n当作每一块的最小大小,假设余数为 r e s res res,那么将剩下的 r e s res res平均分给 r e s res res块,这样所得的答案一定为最小。此时的答案很容易通过简单组合数的方式得到为: r e s ∗ ( n − r e s ) res*(n-res) res(nres)

四、正解程序

#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <cmath>

using namespace std;
typedef long long ll;
const ll maxn=200010;
ll T,n;
ll a[maxn];
int main()
{
	scanf("%lld",&T);
	while(T--)
	{
		scanf("%lld",&n);
		ll sum=0;
		for(ll i=1;i<=n;i++)
		{
			scanf("%lld",&a[i]);
			sum+=a[i];
		}
		ll temp=sum%n;
		printf("%lld\n",temp*(n-temp));
	}
	
	return 0;
}

C. Need for Pink Slips

一、题目链接

https://codeforces.ml/contest/1543/problem/C

二、题目大意

共有 t t t组数据,每组数据中有三个字母 C , M , P C,M,P CMP,初始时每个字母被选中的概率分别为三个浮点数: c , m , p c,m,p cmp,满足 c + m + p = 1 c+m+p=1 c+m+p=1。还有一个浮动指数 v v v

每次会根据概率随机抽取一个字母,规则如下:

  • 如果抽到字母 P P P,游戏结束
  • 否则,根据抽到的那个字母所对应的概率 a a a,与浮动指数 v v v进比较
    • 如果 a ≤ v a≤v av,则这个字母接下来抽到的概率变为 0 0 0,变成不可用字母,其概率将被均匀分配给剩余的可用字母。
    • 如果 a > v a>v a>v,则这个字母接下来抽到的概率将会减 v v v,被减去的概率将被均匀分配给剩余的可用字母。

最后问随机进行抽取游戏,最终抽到的字母个数的期望值是多少。

数据范围: 1 ≤ t ≤ 10 1≤t≤10 1t10 0 < c , m , p < 1 0<c,m,p<1 0<c,m,p<1 0.1 ≤ v ≤ 0.9 0.1≤v≤0.9 0.1v0.9,要求答案的误差范围不超过 1 0 6 10^6 106

三、题目分析

  • 本题其实并不复杂,只需要将题目读懂之后,设置好精度,然后进行暴力递归搜索即可。
  • 当处在某一个状态 c , m , p c,m,p cmp时,分别假设此次选择 C , M C,M CM的情况,根据题目意思设置好下一层递归的状态,然后分别计算答案即可。

四、正解程序

#include<iostream>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <cstdlib>
using namespace std;
typedef long long ll;
const double eps=1e-8;
double solve(double c,double m,double p,double v)
{
	double ans=p;
	if(c>eps)//注意精度问题
	{
		if(c>eps+v)
		{
			if(m>eps)
				ans+=c*(1+solve(c-v,m+v/2,p+v/2,v));
			else
				ans+=c*(1+solve(c-v,0,p+v,v));
		}
		else
		{
			if(m>eps)
				ans+=c*(1+solve(0,m+c/2,p+c/2,v));
			else
				ans+=c*(1+solve(0,0,p+c,v));
		}
	}
	if(m>eps)
	{
		if(m>eps+v)
		{
			if(c>eps)
				ans+=m*(1+solve(c+v/2,m-v,p+v/2,v));
			else
				ans+=m*(1+solve(0,m-v,p+v,v));
		}
		else
		{
			if(c>eps)
				ans+=m*(1+solve(c+m/2,0,p+m/2,v));
			else
				ans+=m*(1+solve(0,0,p+m,v));
		}
	}
	return ans;
}
int main()
{
	ll T;
	scanf("%lld",&T);
	while(T--)
	{
		double c,m,p,v;
		scanf("%lf%lf%lf%lf",&c,&m,&p,&v);
		printf("%.10lf\n",solve(c,m,p,v));
	}
	
	return 0;
}

D1. RPD and Rap Sheet (Easy Version)

一、题目链接

https://codeforces.ml/contest/1543/problem/D1

二、题目大意

此题为交互题,共有 t t t组数据,每组包含一个 n , k n,k nk(easy version里面 k = 2 k=2 k=2),每一次系统会输入一个初始的密码,初始密码是一个在 [ 0 , n − 1 ] [0,n-1] [0n1]的随机值,你可以最多输出n次询问来猜这个密码,如果猜对了密码系统会输入1,否则输入0。

但密码不是不变的,在每次猜密码结束后会根据如下公式变化: 旧密码 ⨁ 新密码 = 询问值 旧密码⨁新密码=询问值 旧密码新密码=询问值

⨁ ⨁ 运算定义为,如果有两个数 A , B A,B AB,对应 k k k进制下的对应位的值为 a , b a,b ab,则对应位的结果为 c = ( a + b )   m o d   k c=(a+b)~mod~k c=(a+b) mod k

数据范围: 1 ≤ t ≤ 1 0 4 1≤t≤10^4 1t104 1 ≤ n ≤ 2 ∗ 1 0 5 1≤n≤2*10^5 1n2105 2 ≤ k ≤ 100 2≤k≤100 2k100

三、题目分析

  • 因为 k = 2 k=2 k=2的情况下,此运算等价于二进制的异或运算,则有 a ⨁ b = c a⨁b=c ab=c a ⨁ c = b a⨁c=b ac=b
  • 所以此时可以将题目中的公式改写为: 旧密码 ⨁ 询问值 = 新密码 旧密码⨁询问值=新密码 旧密码询问值=新密码
  • 考虑多次询问的情况,假设最开始的密码为 x x x,每次猜的密码分别为: a 1 , a 2 , . . . , a n a_{1},a_{2},...,a_{n} a1a2...an。那么将满足: x ⨁ a 1 ⨁ a 2 ⨁ . . . ⨁ a n = 新密码 x⨁a_{1}⨁a_{2}⨁...⨁a_{n}=新密码 xa1a2...an=新密码。而 s u m = a 1 ⨁ a 2 ⨁ . . . ⨁ a n sum=a_{1}⨁a_{2}⨁...⨁a_{n} sum=a1a2...an的值很容易在程序进行的过程中算出来。
  • 所以我们完全可以猜初始密码 x x x,而初始密码是一个在 [ 0 , n − 1 ] [0,n-1] [0n1]的随机值,最多猜 n n n次就能得到答案。

四、正解程序

#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <cmath>

using namespace std;
typedef long long ll;
ll T,n,k;
int main()
{
	scanf("%lld",&T);
	while(T--)
	{
		scanf("%lld%lld",&n,&k);
		ll sum=0,t=0;
		for(ll i=0;i<=n-1;i++)//设初始密码为i
		{
			ll temp=(i^sum);//计算x^a1^a2^...^an
			printf("%lld\n",temp);fflush(stdout);
			scanf("%lld",&t);
			if(t)
				break;
			sum^=temp;//计算a1^a2^...^an
		}
	}
	
	return 0;
}

D2. RPD and Rap Sheet (Hard Version)

一、题目链接

https://codeforces.ml/contest/1543/problem/D2

二、题目大意

同D1。

三、题目分析

  • ⨁ ⨁ 运算定义为,如果有两个数 A , B A,B AB,对应 k k k进制下的对应位的值为 a , b a,b ab,则对应位的结果为 c = ( a + b )   m o d   k c=(a+b)~mod~k c=(a+b) mod k
  • 由于 k k k不一定为2,而经过我们的计算可以得知,此时不可以将公式改写为: 旧密码 ⨁ 询问值 = 新密码 旧密码⨁询问值=新密码 旧密码询问值=新密码
  • 但我们依然可以尝试逆运算,发现 ( c − b )   m o d   k = a (c-b)~mod~k=a (cb) mod k=a,我们设这个运算为: c ㊀ b = a c㊀b=a cb=a。所以我们依然尝试多次询问的展开,看是否存在规律。
  • 考虑多次询问的情况,假设最开始的密码为 x x x,新的密码为 y , z , f y,z,f yzf,每次猜的密码分别为: a 1 , a 2 , . . . , a n a_{1},a_{2},...,a_{n} a1a2...an
    • 那么将满足: x ⨁ y = a 1 x⨁y=a_{1} xy=a1 y ⨁ z = a 2 y⨁z=a_{2} yz=a2 z ⨁ f = a 3 z⨁f=a_{3} zf=a3
    • a 2 ㊀ y = z a_{2}㊀y=z a2y=z a 1 ㊀ x = y a_{1}㊀x=y a1x=y,所以 z = a 2 ㊀ ( a 1 ㊀ x ) z=a_{2}㊀(a_{1}㊀x) z=a2(a1x) f = a 3 ㊀ [ a 2 ㊀ ( a 1 ㊀ x ) ] f=a_{3}㊀[a_{2}㊀(a_{1}㊀x)] f=a3[a2(a1x)]
    • 依次类推,假设我们仍然可以使用Easy Version中的 x , s u m x,sum xsum得到我们答案。 s u m sum sum表示式子完全展开一连串 a i a_{i} ai的运算我们发现:
      • i i i为奇数时, 新密码 = s u m i ㊀ x 新密码=sum_{i}㊀x 新密码=sumix
      • i i i为偶数时, 新密码 = s u m i ⨁ x 新密码=sum_{i}⨁x 新密码=sumix
      • s u m sum sum可以通过以下得到 s u m i = a i ㊀ s u m i − 1 sum_{i}=a_{i}㊀sum_{i-1} sumi=aisumi1
  • 所以我们完全可以猜初始密码 x x x,而初始密码是一个在 [ 0 , n − 1 ] [0,n-1] [0n1]的随机值,最多猜 n n n次就能得到答案。

四、正解程序

#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <cmath>

using namespace std;
typedef long long ll;
ll T,n,k;
ll add(ll x,ll y)
{
	ll times=1,ans=0;
	while(x>0 || y>0)
	{
		ll t1=x%k;
		ll t2=y%k;
		ans=ans+(t1+t2)%k*times;
		x/=k;
		y/=k;
		times*=k;
	}
	return ans;
}
ll sub(ll x,ll y)
{
	ll times=1,ans=0;
	while(x>0 || y>0)
	{
		ll t1=x%k;
		ll t2=y%k;
		ans=ans+(t1-t2+k)%k*times;
		x/=k;
		y/=k;
		times*=k;
	}
	return ans;
}
int main()
{
	scanf("%lld",&T);
	while(T--)
	{
		scanf("%lld%lld",&n,&k);
		ll sum=0,t=0;
		for(ll i=0;i<=n-1;i++)
		{
			ll temp;
			if(i&1)
				temp=sub(sum,i);
			else
				temp=add(sum,i);
			printf("%lld\n",temp);fflush(stdout);
			scanf("%lld",&t);
			if(t)
				break;
			sum=sub(temp,sum);
		}
	}
	
	return 0;
}

E. The Final Pursuit

一、题目链接

https://codeforces.ml/contest/1543/problem/E

二、题目大意

共有t组数据,每组数据用点对表示边的方式给出一个 n n n维超立方体。

定义一个普通 n n n维超立方体,每个点有一个编号,两个点相连,当且仅当,两个点编号在二进制下只有一位不同。所以一个普通 n n n维超立方体是唯一的。而我们可以将一个普通 n n n维超立方体通过一个序列对应到题目给出的立方体。现要求求出其中一个序列。

现在给每一个点进行着色,共有 n n n种颜色可供选择,要求最后着色的结果中,与一个点相邻的 n n n个点有 n n n种颜色。

数据范围: 1 ≤ t ≤ 4096 1≤t≤4096 1t4096 1 ≤ n ≤ 16 1≤n≤16 1n16

三、题目分析

  • 寻找一个合适的序列可以通过暴力贪心的方式去得到,这里不予以证明。
  • 涂色的时候,我们完全可以先涂色一个普通 n n n维超立方体,然后再通过我们得到的序列去得到原图的涂色情况。
  • 如果 n n n不为 2 2 2的幂,则不存在合法的涂色方案。
  • 对于一个普通 n n n维超立方体,如果这个点的编号的二进制表达为: b n − 1 b n − 2 … b 2 b 1 b 0 b_{n-1}b_{n-2}\ldots b_2 b_1 b_0 bn1bn2b2b1b0,它的涂色方案为: ⨁ i = 0 n − 1 i ⋅ b i \bigoplus\limits_{i=0}^{n-1} i\cdot b_i i=0n1ibi。下面予以证明:
    • 显然 0 ≤ ⨁ i = 0 n − 1 i ⋅ b i ≤ n − 1 0≤\bigoplus\limits_{i=0}^{n-1} i\cdot b_i≤n-1 0i=0n1ibin1是成立的。
    • 若两个点 u , v u,v uv相连,颜色为 c 1 , c 2 c1,c2 c1c2,那么 v = u ⊕ ( 1 ≪ ( c 1 ⊕ c 2 ) ) v=u\oplus(1\ll (c_1\oplus c_2)) v=u(1(c1c2))。此时 u , v u,v uv只有在 ( c 1 ⊕ c 2 ) (c_1\oplus c_2) (c1c2)这一位不相同,显然满足相连的关系。

四、正解程序

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <set>
#include <vector>

using namespace std;
typedef long long ll;
const ll maxm=100010;
ll n,m;
ll p[maxm];
bool used[maxm];
set<ll> s[maxm];
vector<ll> adj[maxm];
void permuteHypercube()
{
	memset(p,-1,sizeof(p));
	memset(used,false,sizeof(used));
	p[0]=0;
	used[0]=true;
	for(ll u=1;u<m;u++)
	{
		vector<ll> req;
		for(ll i=0;i<n;i++)//找到在simple中直接相连的点,从大到小 
		{
			ll v=u^(1<<i);
			if(v<u)//因为只有比它小的才已经完成了编号 
				req.push_back(v);
		}
        ll v=req[0];
		for(ll i=0;i<adj[p[v]].size();i++)
		{
			ll w=adj[p[v]][i];//在simple中u,v相连,所以原图中p[v],p[u]相连,又因为在原图中,p[v],w相连,所以p[u]=w 
			if(used[w])
				continue;
			if(req.size()==1 || s[w].find(p[req[1]])!=s[w].end())//w连接的点由两个u都连了,那么就可以说明p[u]为w 
			{
				p[u]=w;
				used[w]=true;
				break;
			}
		}
    }
}
int main()
{
	ll T;
	scanf("%lld",&T);
	while(T--)
	{
		for(ll i=0;i<maxm;i++)
			adj[i].clear(),s[i].clear();
		scanf("%lld",&n);
		m=(1<<n);
		for(ll i=0;i<n*m/2;i++)
		{
			ll u,v;
			scanf("%lld%lld",&u,&v);
			adj[u].push_back(v);
			adj[v].push_back(u);
			s[u].insert(v);
			s[v].insert(u);
		}
		permuteHypercube();
		for(ll i=0;i<m;i++)
			printf("%lld ",p[i]);
		printf("\n");
		if(n!=1 && n!=2 && n!=4 && n!=8 && n!=16)
		{
			printf("-1\n");
			continue;
		}
		ll simple[maxm],ans[maxm];
		for(ll i=0;i<m;i++)//先给simple着色 
		{
			ll color=0;
			for(ll j=0;j<n;j++)
				if(i&(1<<j))
					color=color^j;
			simple[i]=color;
		}
		for(ll i=0;i<m;i++)
			ans[p[i]]=simple[i];
		for(ll i=0;i<m;i++)
			printf("%lld ",ans[i]);
		printf("\n");
	}
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值