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 2≤a<b≤P
解题思路:
因为除了 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 (p−1)同样等于 1 1 1,所以答案就是2和 p − 1 p-1 p−1。
代码:
#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) (i−1,j−1)上有敌军,则可以移动到 ( i − 1 , j − 1 ) (i-1,j-1) (i−1,j−1)。
- 如果 ( i − 1 , j ) (i-1,j) (i−1,j)上没有敌军,则可以移动到 ( i − 1 , j ) (i-1,j) (i−1,j)。
- 如果 ( i − 1 , j + 1 ) (i-1,j+1) (i−1,j+1)上有敌军,则可以移动到 ( i − 1 , j + 1 ) (i-1,j+1) (i−1,j+1)。
问最多有多少士兵可以走到第一行。
解题思路:
因为只有第一行有敌军,所以我们可以把棋盘看成一个 2 × n 2\times n 2×n的棋盘,士兵在第二行,敌军在第一行。
我们按照从左到右的顺序考虑每个士兵,对于位于 ( 2 , i ) (2,i) (2,i)上的士兵:
- 如果 ( 1 , i − 1 ) (1,i-1) (1,i−1)上面有敌军,那么只有当前士兵能够到达,右边的士兵无法到达。
- 如果 ( 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个询问,每个询问有三种类型:
- 添加 a a a与 b b b之间的友谊关系
- 删除 a a a与 b b b之间的友谊关系
- 输出执行以下过程之后剩余的贵族数量。
过程:每个脆弱的贵族会被杀死直到不存在脆弱的贵族,每个脆弱的贵族死亡之后可能会产生新的贵族。
解题思路:
可以把本题看成一个拓扑排序的问题,把所有由友谊关系看成低权力连向高权力的一条有向边,所有入度为 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(∣ai−ai+1∣,∣ai+1−ai+2∣,....,∣aj−1−aj∣)>1。
设 b i = ∣ a i − a i + 1 ∣ , 1 ≤ i < n b_i=|a_i-a_{i+1}|,1\le i< n bi=∣ai−ai+1∣,1≤i<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 i−j+2,因为当前 b b b数组是在 a a a数组变化过来的,所以长度是 i − j + 1 i-j+1 i−j+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;
}