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 1≤t≤5×103, 0 ≤ a , b ≤ 1 0 18 0≤a,b≤10^{18} 0≤a,b≤1018。
三、题目分析
- 首先我们假设 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,b−a)的结果相同,那么很显然,答案所需的第一个值一定是 b − a b-a b−a。
- 然后考虑如何让操作次数最小,显然此处有两种答案,一种是 x x x为负,使得 b + x b+x b+x为 b − a b-a b−a的倍数,一种是 x x x为正,使得 b + x b+x b+x为 b − a b-a b−a的倍数。分别求出两种方案的 ∣ 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=1n∑j=i+1n∣a[i]−a[j]∣的值尽可能小。输出最后的最小值。
数据范围: 1 ≤ t ≤ 1 0 4 1≤t≤10^4 1≤t≤104, 1 ≤ n ≤ 2 × 1 0 5 1≤n≤2\times10^5 1≤n≤2×105, 0 ≤ a [ i ] ≤ 1 0 9 0≤a[i]≤10^{9} 0≤a[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∗(n−res)。
四、正解程序
#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 C,M,P,初始时每个字母被选中的概率分别为三个浮点数: c , m , p c,m,p c,m,p,满足 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 a≤v,则这个字母接下来抽到的概率变为 0 0 0,变成不可用字母,其概率将被均匀分配给剩余的可用字母。
- 如果 a > v a>v a>v,则这个字母接下来抽到的概率将会减 v v v,被减去的概率将被均匀分配给剩余的可用字母。
最后问随机进行抽取游戏,最终抽到的字母个数的期望值是多少。
数据范围: 1 ≤ t ≤ 10 1≤t≤10 1≤t≤10, 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.1≤v≤0.9,要求答案的误差范围不超过 1 0 6 10^6 106。
三、题目分析
- 本题其实并不复杂,只需要将题目读懂之后,设置好精度,然后进行暴力递归搜索即可。
- 当处在某一个状态 c , m , p c,m,p c,m,p时,分别假设此次选择 C , M C,M C,M的情况,根据题目意思设置好下一层递归的状态,然后分别计算答案即可。
四、正解程序
#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 n,k(easy version里面 k = 2 k=2 k=2),每一次系统会输入一个初始的密码,初始密码是一个在 [ 0 , n − 1 ] [0,n-1] [0,n−1]的随机值,你可以最多输出n次询问来猜这个密码,如果猜对了密码系统会输入1,否则输入0。
但密码不是不变的,在每次猜密码结束后会根据如下公式变化: 旧密码 ⨁ 新密码 = 询问值 旧密码⨁新密码=询问值 旧密码⨁新密码=询问值。
⨁ ⨁ ⨁运算定义为,如果有两个数 A , B A,B A,B,对应 k k k进制下的对应位的值为 a , b a,b a,b,则对应位的结果为 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 1≤t≤104, 1 ≤ n ≤ 2 ∗ 1 0 5 1≤n≤2*10^5 1≤n≤2∗105, 2 ≤ k ≤ 100 2≤k≤100 2≤k≤100。
三、题目分析
- 因为 k = 2 k=2 k=2的情况下,此运算等价于二进制的异或运算,则有 a ⨁ b = c a⨁b=c a⨁b=c时 a ⨁ c = b a⨁c=b a⨁c=b。
- 所以此时可以将题目中的公式改写为: 旧密码 ⨁ 询问值 = 新密码 旧密码⨁询问值=新密码 旧密码⨁询问值=新密码
- 考虑多次询问的情况,假设最开始的密码为 x x x,每次猜的密码分别为: a 1 , a 2 , . . . , a n a_{1},a_{2},...,a_{n} a1,a2,...,an。那么将满足: x ⨁ a 1 ⨁ a 2 ⨁ . . . ⨁ a n = 新密码 x⨁a_{1}⨁a_{2}⨁...⨁a_{n}=新密码 x⨁a1⨁a2⨁...⨁an=新密码。而 s u m = a 1 ⨁ a 2 ⨁ . . . ⨁ a n sum=a_{1}⨁a_{2}⨁...⨁a_{n} sum=a1⨁a2⨁...⨁an的值很容易在程序进行的过程中算出来。
- 所以我们完全可以猜初始密码 x x x,而初始密码是一个在 [ 0 , n − 1 ] [0,n-1] [0,n−1]的随机值,最多猜 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 A,B,对应 k k k进制下的对应位的值为 a , b a,b a,b,则对应位的结果为 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 (c−b) mod k=a,我们设这个运算为: c ㊀ b = a c㊀b=a c㊀b=a。所以我们依然尝试多次询问的展开,看是否存在规律。
- 考虑多次询问的情况,假设最开始的密码为
x
x
x,新的密码为
y
,
z
,
f
y,z,f
y,z,f,每次猜的密码分别为:
a
1
,
a
2
,
.
.
.
,
a
n
a_{1},a_{2},...,a_{n}
a1,a2,...,an。
- 那么将满足: x ⨁ y = a 1 x⨁y=a_{1} x⨁y=a1, y ⨁ z = a 2 y⨁z=a_{2} y⨁z=a2, z ⨁ f = a 3 z⨁f=a_{3} z⨁f=a3。
- a 2 ㊀ y = z a_{2}㊀y=z a2㊀y=z, a 1 ㊀ x = y a_{1}㊀x=y a1㊀x=y,所以 z = a 2 ㊀ ( a 1 ㊀ x ) z=a_{2}㊀(a_{1}㊀x) z=a2㊀(a1㊀x), f = a 3 ㊀ [ a 2 ㊀ ( a 1 ㊀ x ) ] f=a_{3}㊀[a_{2}㊀(a_{1}㊀x)] f=a3㊀[a2㊀(a1㊀x)]。
- 依次类推,假设我们仍然可以使用Easy Version中的
x
,
s
u
m
x,sum
x,sum得到我们答案。
s
u
m
sum
sum表示式子完全展开一连串
a
i
a_{i}
ai的运算我们发现:
- i i i为奇数时, 新密码 = s u m i ㊀ x 新密码=sum_{i}㊀x 新密码=sumi㊀x。
- i i i为偶数时, 新密码 = s u m i ⨁ x 新密码=sum_{i}⨁x 新密码=sumi⨁x。
- 而 s u m sum sum可以通过以下得到 s u m i = a i ㊀ s u m i − 1 sum_{i}=a_{i}㊀sum_{i-1} sumi=ai㊀sumi−1。
- 所以我们完全可以猜初始密码 x x x,而初始密码是一个在 [ 0 , n − 1 ] [0,n-1] [0,n−1]的随机值,最多猜 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 1≤t≤4096, 1 ≤ n ≤ 16 1≤n≤16 1≤n≤16。
三、题目分析
- 寻找一个合适的序列可以通过暴力贪心的方式去得到,这里不予以证明。
- 涂色的时候,我们完全可以先涂色一个普通的 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
bn−1bn−2…b2b1b0,它的涂色方案为:
⨁
i
=
0
n
−
1
i
⋅
b
i
\bigoplus\limits_{i=0}^{n-1} i\cdot b_i
i=0⨁n−1i⋅bi。下面予以证明:
- 显然 0 ≤ ⨁ i = 0 n − 1 i ⋅ b i ≤ n − 1 0≤\bigoplus\limits_{i=0}^{n-1} i\cdot b_i≤n-1 0≤i=0⨁n−1i⋅bi≤n−1是成立的。
- 若两个点 u , v u,v u,v相连,颜色为 c 1 , c 2 c1,c2 c1,c2,那么 v = u ⊕ ( 1 ≪ ( c 1 ⊕ c 2 ) ) v=u\oplus(1\ll (c_1\oplus c_2)) v=u⊕(1≪(c1⊕c2))。此时 u , v u,v u,v只有在 ( c 1 ⊕ c 2 ) (c_1\oplus c_2) (c1⊕c2)这一位不相同,显然满足相连的关系。
四、正解程序
#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;
}