Codeforces Round #736 (Div. 2) 题解(A-D)

本文提供了Codeforces Round #736 (Div.2) A-D 四题的详细解题思路及代码实现,包括数学问题、棋盘策略游戏、图论和数组处理等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Codeforces Round #736 (Div. 2) 题解(A-D)

A. Gregor and Cryptography

题目大意:

给出一个质数 p p p,找出满足以下两个条件的 a a a b b b

  • p m o d      a = p m o d      b p \mod \ a = p \mod \ b pmod a=pmod b
  • 2 ≤ a < b ≤ P 2\le a < b \le P 2a<bP

解题思路:

因为除了 2 2 2之外,所有的质数都是奇数,本题 p p p的范围是 [ 5 , 1 0 9 ] [5,10^9] [5,109]

而奇数模 2 2 2是肯定等于 1 1 1的,而且 p   m o d   ( p − 1 ) p \ mod \ (p-1) p mod (p1)同样等于 1 1 1,所以答案就是2和 p − 1 p-1 p1

代码:

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int T;
    cin>>T;
    while(T--){
        int p;
        cin>>p;
        cout<<2<<" "<<p-1<<endl;
    }
    return 0;
}

B. Gregor and the Pawn Game

题目大意:

在一个 n × n n\times n n×n的棋盘上,最后一行放置着若干个士兵,第一行放置着若干个敌军。

对于位于 ( i , j ) (i,j) (i,j)的士兵,每次可以执行以下的移动操作:

  • 如果 ( i − 1 , j − 1 ) (i-1,j-1) (i1,j1)上有敌军,则可以移动到 ( i − 1 , j − 1 ) (i-1,j-1) (i1,j1)
  • 如果 ( i − 1 , j ) (i-1,j) (i1,j)上没有敌军,则可以移动到 ( i − 1 , j ) (i-1,j) (i1,j)
  • 如果 ( i − 1 , j + 1 ) (i-1,j+1) (i1,j+1)上有敌军,则可以移动到 ( i − 1 , j + 1 ) (i-1,j+1) (i1,j+1)

问最多有多少士兵可以走到第一行。

解题思路:

因为只有第一行有敌军,所以我们可以把棋盘看成一个 2 × n 2\times n 2×n的棋盘,士兵在第二行,敌军在第一行。

我们按照从左到右的顺序考虑每个士兵,对于位于 ( 2 , i ) (2,i) (2,i)上的士兵:

  • 如果 ( 1 , i − 1 ) (1,i-1) (1,i1)上面有敌军,那么只有当前士兵能够到达,右边的士兵无法到达。
  • 如果 ( 1 , i ) (1,i) (1,i)上面没有敌军,只有当前士兵能够到达,右边的士兵无法到达。
  • 如果(1,i)上面有敌军,除了当前士兵能够到达之外,位于 ( 2 , i + 2 ) (2,i+2) (2,i+2)的士兵同样能够到达。

所以很容易想到一个贪心策略,优先考虑当前士兵能否走到左上方和正上方,最后再考虑能否走到右上方。

时间复杂度 O ( N ) O(N) O(N)

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n;
char a[N],b[N];
int main()
{
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        scanf("%s%s",b+1,a+1);
        int res=0;
        for(int i=1;i<=n;i++){
            if(a[i]=='0') continue;
            if(b[i-1]=='1'){
                res++;
                b[i-1]='0';
            }
            else if(b[i]=='0') res++;
            else if(i<n&&b[i+1]=='1'){
                res++;
                b[i+1]='0';
            }
        }
        printf("%d\n",res);
    }
    return 0;
}

C. Web of Lies

题目大意:

n n n个贵族,编号 [ 1 , n ] [1,n] [1,n],每个贵族的权力大小等于他的编号。他们之前存在 m m m对友谊关系,友谊关系是双向的。

给出一个脆弱的贵族定义:

  • 这个贵族至少有一个朋友
  • 并且这个贵族所有朋友的权力都比他大

现在要处理 q q q个询问,每个询问有三种类型:

  1. 添加 a a a b b b之间的友谊关系
  2. 删除 a a a b b b之间的友谊关系
  3. 输出执行以下过程之后剩余的贵族数量。

过程:每个脆弱的贵族会被杀死直到不存在脆弱的贵族,每个脆弱的贵族死亡之后可能会产生新的贵族。

解题思路:

可以把本题看成一个拓扑排序的问题,把所有由友谊关系看成低权力连向高权力的一条有向边,所有入度为 0 0 0的点即为脆弱的贵族,出度为 0 0 0的点就是最后存活的贵族。

所以本道题的解法就显而易见了。

首先对于 m m m对友谊关系加有向边,记录每个点的出度和入度,出度为0的点的数量就是初始状态下执行过程的贵族存活数量。

对于每次加边和删边的操作,只需要对入度和出度进行修改,判断出度为0的点是否发生变化,实时更新答案就行。

因为本题没有用到入度,关于入度的所以操作可以去掉。

时间复杂度 O ( n + m + q ) O(n+m+q) O(n+m+q)

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int dout[N];
int n,m,q;
int main()
{
    scanf("%d%d",&n,&m);
    while(m--){
        int a,b;
        scanf("%d%d",&a,&b);
        if(a>b) swap(a,b);
        dout[a]++;
    }
    int res=0;
    for(int i=1;i<=n;i++)
        if(!dout[i]) res++;
    scanf("%d",&q);
    while(q--){
        int op,a,b;
        scanf("%d",&op);
        if(op==1){
            scanf("%d%d",&a,&b);
            if(a>b) swap(a,b);
            if(!dout[a]) res--;
            dout[a]++;
        }
        else if(op==2){
            scanf("%d%d",&a,&b);
            if(a>b) swap(a,b);
            dout[a]--;
            if(!dout[a]) res++;
        }
        else printf("%d\n",res);
    }
    return 0;
}

D. Integers Have Friends

题目大意:

给出一个长度为 n n n的数组 a a a,找出最长的子数组 a [ i , j ] a[i,j] a[i,j]满足以下条件:

  • 存在一个大于等于2的 m m m使得$a_i\ mod \ m = a_{i+1} \ mod \ m=…=a_j\ mod \ m $。

输出满足以上条件的最长子数组的长度。

解题思路:

题中的条件经过思考一番可以发现等价于 g c d ( ∣ a i − a i + 1 ∣ , ∣ a i + 1 − a i + 2 ∣ , . . . . , ∣ a j − 1 − a j ∣ ) > 1 gcd(|a_i-a_{i+1}|,|a_{i+1}-a_{i+2}|,....,|a_{j-1}-a_j|)>1 gcd(aiai+1,ai+1ai+2,....,aj1aj)>1

b i = ∣ a i − a i + 1 ∣ , 1 ≤ i < n b_i=|a_i-a_{i+1}|,1\le i< n bi=aiai+1,1i<n,就可以把原问题转化为找出 b b b数组中最长的一段 g c d > 1 gcd>1 gcd>1的连续区间。

假设我们已经知道了每一段区间的最大公约数,我们是否有办法求出最大子数组的长度呢?

这时我们可以采取使用双指针的做法,对于每一个右端点 i i i,如果区间 [ j , i ] [j,i] [j,i] g c d = 1 gcd=1 gcd=1我们就右移左端点,直到 [ j , i ] [j,i] [j,i]的最大公约数大于1。

我们就得到了以 i i i为右端点的最长子数组的长度 i − j + 2 i-j+2 ij+2,因为当前 b b b数组是在 a a a数组变化过来的,所以长度是 i − j + 1 i-j+1 ij+1再加 1 1 1

这里还有一种特殊的情况,就是 b i b_i bi本身等于 1 1 1,当左右端点汇合的时候最大公约数仍然是 1 1 1,所以这里需要判断一下,不能无脑更新答案。

这里我们如果已经知道了所有的区间 g c d gcd gcd,双指针的做法时间复杂度是 O ( n ) O(n) O(n)的,是没问题的。

那么问题来了,我们如何得到每一段区间的 g c d gcd gcd呢?

这里可以采用ST表和线段树来帮助我们预处理出所有区间的 g c d gcd gcd,其中初始化的时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn),ST表的查询是 O ( 1 ) O(1) O(1)的,线段树的查询是 O ( l o g n ) O(logn) O(logn)的,显然不管哪种数据结构都是能够够用的。

代码:

ST表版本(343ms):
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=2e5+10,M=20;
LL a[N],f[N][M];
int n;
LL gcd(LL a,LL b){
    return b?gcd(b,a%b):a;
}
LL query(int l,int r){
    int k=log2(r-l+1);
    return gcd(f[l][k],f[r-(1<<k)+1][k]);
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
        n--;
        for(int i=1;i<=n;i++) f[i][0]=abs(a[i]-a[i+1]);
        for(int j=1;j<=log2(n);j++){
            for(int i=1;i+(1<<j)-1<=n;i++){
                f[i][j]=gcd(f[i][j-1],f[i+(1<<j-1)][j-1]);
            }
        }
        int res=1;
        for(int i=1,j=1;i<=n;i++){
            while(j<i&&query(j,i)<=1) j++;
            if(query(j,i)>1) res=max(res,i-j+2);
        }
        printf("%d\n",res);
    }
    return 0;
}
线段树版本(312ms):
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=2e5+10;
struct Node{
    int l,r;
    LL d;
}tr[N<<2];
LL a[N],b[N];
int n;
LL gcd(LL a,LL b){
    return b?gcd(b,a%b):a;
}
void pushup(int u){
    tr[u].d=gcd(tr[u<<1].d,tr[u<<1|1].d);
}
void build(int u,int l,int r){
    if(l==r)  tr[u]={l,r,b[l]};
    else{
        tr[u]={l,r};
        int mid=l+r>>1;
        build(u<<1,l,mid);build(u<<1|1,mid+1,r);
        pushup(u);
    }
}
LL query(int u,int l,int r){
    if(l<=tr[u].l&&tr[u].r<=r) return tr[u].d;
    int mid=tr[u].l+tr[u].r>>1;

    if(r<=mid) return query(u<<1,l,r);
    else if(l>mid) return query(u<<1|1,l,r);
    else return gcd(query(u<<1,l,r),query(u<<1|1,l,r));
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
        n--;
        for(int i=1;i<=n;i++) b[i]=(abs(a[i]-a[i+1]));
        if(!n){//需要特判一下,不然后面初始化线段树的时候会出错
            puts("1");
            continue;
        }
        
        build(1,1,n);
        int res=1;
        for(int i=1,j=1;i<=n;i++){
            while(j<i&&query(1,j,i)<=1) j++;
            if(query(1,j,i)>1) res=max(res,i-j+2);
        }
        printf("%d\n",res);
    }
    return 0;
}
抱歉,根据提供的引用内容,我无法理解你具体想要问什么问题。请提供更清晰明确的问题,我将竭诚为你解答。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Codeforces Round 860 (Div. 2)题解](https://blog.csdn.net/qq_60653991/article/details/129802687)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [【CodeforcesCodeforces Round 865 (Div. 2) (补赛)](https://blog.csdn.net/t_mod/article/details/130104033)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [Codeforces Round 872 (Div. 2)(前三道](https://blog.csdn.net/qq_68286180/article/details/130570952)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值