1025div2
Codeforces Round 1025 (Div. 2)
A. It's Time To Duel
题意
有 n 个人,第 i 个人与第 i+1 个人进行决斗(没有平局),赢过至少一次的人\(a_i=1\),一次都没赢的人\(a_i=0\),给出最后的\(a_i\),判断是否合法(不合法是输出YES)
思路
由于n个人中除了 i=1 和 i=n ,都要进行两次决斗,当\(a_i=0\)时 \(a_{i-1},a_{i+1}\) 必然为1,所以不能有相邻的0
0不能相邻,但1可以,只是需要让一段相邻的1左右至少有一个0(等价于\(a_i\)中必须出现0,做题的时候并没有想到这一层)
代码
#include<bits/stdc++.h>
using namespace std;
int T,n;
int a[200100];
int main()
{
scanf("%d",&T);
while(T--) {
int f=0;
scanf("%d",&n);
for(int i=1;i<=n;i++) {
scanf("%d",&a[i]);
}
int flag0=0;
for(int i=1;i<n;i++) {
if(a[i]==0) {
if(a[i+1]==0) {
f=1;
printf("YES\n");
break;
}
flag0=1;
}
if(a[i]==1&&a[i+1]==1) {
if(n==2) {
f=1;
printf("YES\n");
break;
}
if(!flag0) {
for(i=i+1;i<=n;i++) {
if(a[i]==0) {
flag0=1;
break;
}
}
if(!flag0) {
f=1;
printf("YES\n");
break;
} else {
i--;
}
}
}
}
if(!f) {
printf("NO\n");
}
}
return 0;
}
B. Slice to Survive
题意
n*m的网格里有一个人,我们每次可以画一条线分割网格(舍弃没有人的那一部分),然后人可以移动至新网格的任意位置,重复此流程直至网格被分割至1*1,统计画线分割的次数为ans,我们希望ans尽可能小,网格里的人希望ans尽可能大,求最终ans

思路
显然最好的分割一定是紧贴着F的四条边之一的,那么每次切割都是在四选一
四条边的优先级取决于该边到边界的距离,分别是 I-N, n-I, J-M, m-J(n,m,N,M,分别为网格的上下界,命名习惯有点差),定义这个距离为dis
每次分割选择dis最大的一条边,更新新网格的边界,然后 F 跳到网格的中心,完成一次循环,直到边界重叠
需要注意的是在第一次分割时,F并不在中心,此时的dis并不是最好的判断依据,我们需要枚举第一次选取的四条边,取最小的ans
题解和这个方法有些差别,直接抽象成上下和左右两个方向,不过本质是一样的,左右之间互不影响,然后取最大值
代码
#include<bits/stdc++.h>
using namespace std;
int T,CNT=0;
int solve(int n,int m,int N,int M) {
int cnt=0;
int I,J;
while(N!=n||M!=m) {
I=(N+n)/2;
J=(M+m)/2;
int w=I-N;
int s=n-I;
int a=J-M;
int d=m-J;
int maxn=max({w,a,s,d});
if(w==maxn) {
N=I;
} else if(s==maxn) {
n=I;
} else if(a==maxn) {
M=J;
} else {
m=J;
}
//printf("%3d %3d\n%3d %3d\n",N,n,M,m);
cnt++;
}
return cnt;
}
int main()
{
scanf("%d",&T);
while(T--) {
int I,J;
int cnt=0;
int N=1,M=1;
int n,m;
scanf("%d%d%d%d",&n,&m,&I,&J);
printf("%d\n",min({solve(n,J,1,1),solve(I,m,1,1),solve(n,m,I,1),solve(n,m,1,J)})+1);
}
return 0;
}
C1. Hacking Numbers (Easy Version)
题意
有一个未知数x和一个给定的目标值n,我们可以使用以下四个命令将x变成n,同时对使用的命令的次数也有限制
在这个version,限制最多使用 7 条命令
(表中的S(x)为对每一位的数字进行求和,如S(123)=1+2+3=6 )
| Command | Constraint | Result | Case | Update | Jury's response |
|---|---|---|---|---|---|
| "add y" | \(−10^{18}≤y≤10^{18}\) | res=x+y | if \(1≤res≤10^{18}\) | x←res | "1" |
| else | x←x | "0" | |||
| "mul y" | \(1≤y≤10^{18}\) | res=x⋅y | if \(1≤res≤10^{18}\) | x←res | "1" |
| else | x←x | "0" | |||
| "div y" | \(1≤y≤10^{18}\) | res=x/y | if y divides x | x←res | "1" |
| else | x←x | "0" | |||
| "digit" | — | res=S(x) | — | x←res | "1" |
思路
在第一时间我们想到的当然是通过二分锁定x的值,然后再add或者mul上去,但显然7-1次二分查找只对\(2^7=128\)以内的数有效
而digit显然能帮助我们快速把x的值缩小(digit会更新x的值),这对二分很有帮助,通过枚举\(1-10^9\)的每个数发现三次digit可以把这个范围的数缩小至个位数,但之后仅剩7-3-1=3次二分的机会,不足以涵盖1-9中的9个数
更进一步,如果digit一次,x为1-81;digit两次,x为1-12。恰好在digit两次时,7-2-1=4次二分能涵盖12个数。
关于二分的实现有许多方法,比如根据response,通过add负数二分、通过add大数二分、通过div二分(只对小范围有效),但最简洁的方法是直接add -8, -4, -2, -1,因为res≤0时不更新x,一轮操作直接将x变为1,然后add n-1即可
代码
#include<bits/stdc++.h>
using namespace std;
int T,n;
int main()
{
//fflush(stdin);
//fflush(stdout);
scanf("%d",&T);
while(T--) {
int opt;
scanf("%d",&n);
cout<<"digit\n";
cin>>opt;//1-81
cout<<"digit\n";
cin>>opt;
for(int i=8;i>=1;i/=2) {
cout<<"add "<<-i<<"\n";
cin>>opt;
}
cout<<"mul "<<n<<"\n";
cin>>opt;
cout<<"!\n";
cin>>opt;
}
return 0;
}
C2. Hacking Numbers (Medium Version)
题意
和easy版本不同的是指令数量限制为4
思路
指令数不超过4,则只有3次机会对x进行确认,显然此时digit两次不能满足限制
题解给出了一个9,这个数有一些有用的性质:
1. 一个数如果 m 是 9 的倍数,则 S(m) 也是 9 的倍数
2. n是1-1e9的数,在这个范围里S(n)为1-81,结合1可以得出如果 n 是 9 的倍数,则S(n)为9,18,27,36,45,54,63,72,81中的一个
所以我们mul 9让n变成9的倍数,此时n为9-9e9,如果此时S(x)比81大的话,根据性质1只能为90,10位数中只有10个9满足,但超过了9*n,故S(x)取值范围不变
此外,S(S(x))=9,此时 x 被确定为9,add n-9即可
所以指令顺序为:
mul 9
digit
digit
add n-9
代码
#include<bits/stdc++.h>
using namespace std;
int T,n;
int main()
{
//fflush(stdin);
//fflush(stdout);
scanf("%d",&T);
while(T--) {
int opt;
scanf("%d",&n);
printf("mul 9\n");
cin>>opt;
printf("digit\n");
cin>>opt;
printf("digit\n");
cin>>opt;
printf("add %d\n",n-9);
cin>>opt;
printf("!\n");
cin>>opt;
}
return 0;
}
C3. Hacking Numbers (Hard Version)
题意
和前两个版本不同的是不给定指令数量限制,但要求使用最小的指令数量
思路
由前面的版本中可以得到一些思路,在C2的题解中提到了 9 是同时和mul与digit有关联的数,在C3里当然不能取9,但可以进一步想到寻找其他和9有类似功能的数
思考未果看题解,题解给出了一个性质:\(S(x\times(10^d-1))=9 \times d,∀x∈[1,10^d]\)(不是这也能找规律啊?)
所以我们只要让x mul 999999999(1e9-1)就能让1-1e10的x mul一次之后变成81
即流程为:
mul 999999999
digit
add n-81
此外,需要注意的是如果n为81则不需要add了
写这种题真的是伤脑筋,不看题解写不出,看了题解感觉没收获(恼)
代码
#include<bits/stdc++.h>
using namespace std;
int T,n;
int main()
{
//fflush(stdin);
//fflush(stdout);
scanf("%d",&T);
while(T--) {
int opt;
scanf("%d",&n);
printf("mul 999999999\n");
cin>>opt;
printf("digit\n");
cin>>opt;
if(n!=81) {
printf("add %d\n",n-81);
cin>>opt;
}
printf("!\n");
cin>>opt;
}
return 0;
}

浙公网安备 33010602011771号