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

png

思路

显然最好的分割一定是紧贴着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;
}
posted @ 2025-05-20 01:04  Sonatto  阅读(49)  评论(0)    收藏  举报