Codeforces Round #736 (Div. 2) 部分题题解
D.Integers Have Friends
题目链接
简要题解
我们首先要发现这样一个性质:如果\(a_i\) \(\equiv\) \(a_j(mod\) \(m)\),那么\(a_i-a_j\) \(\equiv\) \(0(mod\) \(m)\)
对于原数列\(a_i\),我们构造一个新数列\(b_i\),使得\(b_i=abs(a_i-a_{i+1})\)。
对于每一个\(b_i\),只要我们选的\(m\)是\(b_i\)的一个约数,那么\(a_i\) \(\equiv\) \(a_{i+1}(mod\) \(m)\)就成立。
因此我们可以把问题进行转化:在\(\{b_i\}\)中求一段最长的连续子序列,使得子序列内所有元素的\(gcd\)大于1。
这个问题我们可以用二分来解决,每次枚举右端点,二分出符合条件的最远的左端点。
这要求我们能够快速计算出一段区间的\(gcd\),预处理一个\(ST\)表就可以了。
代码如下:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+10;
int T,n,Ans,Log[MAXN];
ll A[MAXN],B[MAXN],ST[MAXN][20];
ll Read()
{ ll a=0,c=1; char b=getchar();
while(b!='-'&&(b<'0'||b>'9')) b=getchar();
if(b=='-') c=-1,b=getchar();
while(b>='0'&&b<='9') a=a*10+b-48,b=getchar();
return a*c;
}
ll Abs(ll S){ return S>0?S:-S; }
ll Max(ll A,ll B){ return A>B?A:B; }
ll Gcd(ll A,ll B){ return B?Gcd(B,A%B):A; }
ll Query(ll Le,ll Ri)
{ int S=Log[Ri-Le+1];
return Gcd(ST[Le][S],ST[Ri-(1<<S)+1][S]);
}
int main()
{ for(int i=1;i<MAXN;i++) Log[i]=log2(i);
for(scanf("%d",&T);T;T--)
{ n=Read()-1;
for(int i=1;i<=n+1;i++) A[i]=Read();
for(int i=1;i<=n;i++) B[i]=Abs(A[i]-A[i+1]),ST[i][0]=B[i];
for(int j=1;j<=Log[n];j++)
for(int i=1;i+(1<<j)-1<=n;i++) ST[i][j]=Gcd(ST[i][j-1],ST[i+(1<<(j-1))][j-1]);
for(int i=1;i<=n;i++)
{ int Nv=i;
if(B[i]==1) continue ;
for(int Le=1,Ri=i,Mid;Le<=Ri;)
Mid=(Le+Ri)>>1,Query(Mid,i)>=2?(Nv=Mid,Ri=Mid-1):Le=Mid+1;
Ans=Max(Ans,i-Nv+1);
}
printf("%d\n",Ans+1),Ans=0;
}
}
E.The Three Little Pigs
题目链接
简要题解
我们先来考虑一个询问的情况,看看最终答案是什么样子的。
题目说道,两个攻击方案不同,当且仅当攻击时刻不同,以及攻击对象不同。
那么我们很自然能够想到,枚举攻击时刻,利用组合数来计算攻击对象的方案,求和得到答案。
对于一个询问\(x\),它对应的答案为:$$Ans=\sum_{i=0}^n C_{3*i}^x$$
预处理组合数,每次询问就可以\(O(n)\)解决,但是这并不能通过本题。
由于答案是组合数的和,我们可以从组合数的性质来入手。
有一个众所周知的公式:\(C_i^j=C_{i-1}^j+C_{i-1}^{j-1}\)
把这个公式代入答案的计算公式,我们得到:
这个时候就发现了一些有意思的东西。我们设\(A[x][j]=\sum_{i=0}^{n-(j\neq 0)} C_{3*i+j}^x\),那么上式可以写成
同理,我们可以得到
这三个方程线性相关,我们目前无法求解。
再次利用组合数的性质,我们把\(A[x][0],A[x][1],A[x][2]\)加起来,可以得到:
联立上述四个式子就可以解方程,再预处理组合数,就可以得到\(O(1)\)递推式。
那么思路就很清楚了:我们先得到\(A[0][0],A[0][1],A[0][2]\)的值,然后递推求出所有的\(A[x][0],A[x][1],A[x][2]\)。
对于每一个询问\(x\),我们直接输出\(A[x][0]\)。
时间复杂度\(O(n)\)。
代码如下:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=3e6+10;
const int Mod=1e9+7;
int n,Qs,Ans,Inv3,Two,A[MAXN][3];
int Read()
{ int a=0,c=1; char b=getchar();
while(b!='-'&&(b<'0'||b>'9')) b=getchar();
if(b=='-') c=-1,b=getchar();
while(b>='0'&&b<='9') a=a*10+b-48,b=getchar();
return a*c;
}
int Pow(int Down,int Up)
{ int Ret=1,Now=Down;
for(;Up>=1;Up>>=1) Up&1?Ret=1ll*Ret*Now%Mod:0,Now=1ll*Now*Now%Mod;
return Ret;
}
namespace PRE
{ int Inv[MAXN],Fac[MAXN];
void Prepare()
{ Fac[0]=Inv[0]=1,Inv3=Pow(3,Mod-2),A[0][0]=n+1,A[0][1]=A[0][2]=n;
for(int i=1;i<=3*n+1;i++) Fac[i]=1ll*Fac[i-1]*i%Mod;
Inv[3*n+1]=Pow(Fac[3*n+1],Mod-2);
for(int i=3*n;i>=0;i--) Inv[i]=1ll*Inv[i+1]*(i+1)%Mod;
}
}using namespace PRE;
int C(int A,int B){ return B>A?0:1ll*Fac[A]*Inv[B]%Mod*Inv[A-B]%Mod; }
int main()
{ n=Read(),Qs=Read(),Prepare();
for(int i=1;i<=n*3;i++)
{ int B0=A[i-1][0],B1=A[i-1][1],B2=A[i-1][2],Nc=C(3*n+1,i+1),Sc=C(3*n,i);
B0=(1ll*B0+Mod-C(3*n,i-1))%Mod;
A[i][0]=(1ll*Nc+Sc-B0+B2)*Inv3%Mod,A[i][0]=(A[i][0]+Mod)%Mod;
A[i][1]=(1ll*Nc-2*Sc+2*B0+B2)*Inv3%Mod,A[i][1]=(A[i][1]+Mod)%Mod;
A[i][2]=(1ll*Nc+Sc-B0-2*B2)*Inv3%Mod,A[i][2]=(A[i][2]+Mod)%Mod;
}
for(int i=1,S;i<=Qs;i++) S=Read(),printf("%d\n",A[S][0]);
}
F1.Gregor and the Odd Cows (Easy)
题目链接
Gregor and the Odd Cows (Easy)
简要题解
题目和格点三角形的面积、内部格点数有关,那么大概率需要用到皮克定理解题。
我们设格点多边形的面积为\(S\),多边形内部的格点数为\(a\),多边形边界上的格点数为\(b\),那么根据皮克定理我们有
我们现在的限制有:\(S\)是整数,\(a\)是奇数。
本题还有一个特殊性质:所有点的坐标都是偶数。
由于这个性质,我们可以知道\(S\)一定是一个偶数,因为格点三角形的面积可以认为是正方形面积减去多个直角三角形的面积。
正方形的边长和所有直角三角形的直角边都是偶数,所以它们的面积都是偶数,那么\(S\)也就是偶数了。
由皮克定理我们知道,若\(a\)是奇数,那么\(\frac{b}{2}\)必定是偶数,即\(b\equiv0\)\((mod\) \(4)\)
如何求\(b\)?
我们设三个顶点为\((x_1,y_1)\),\((x_2,y_2)\),\((x_3,y_3)\),那么
这个式子画一下图就很容易理解了。
我们只关心模\(4\)之后的结果,并且所有坐标都是偶数,于是可以直接将坐标模\(4\)。
现在我们的问题变成了:给定若干个位于\((0,0),(0,2),(2,0),(2,2)\)上的点,求在其中选三个点使得\(b\equiv0\)\((mod\) \(4)\)的方案数。
情况数很少,稍微玩一下就可以知道,只要选定的三个点中,至少有两个点位于同一坐标即可。
时间复杂度\(O(n)\)
代码如下:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,Cnt[4][4];
ll Ans;
ll C2(int S){ return 1ll*S*(S-1)/2; }
ll C3(int S){ return 1ll*S*(S-1)*(S-2)/6; }
int main()
{ scanf("%d",&n);
for(int i=1,X,Y;i<=n;i++) scanf("%d%d",&X,&Y),Cnt[X%4][Y%4]++;
Ans=C3(Cnt[0][0])+C3(Cnt[0][2])+C3(Cnt[2][0])+C3(Cnt[2][2]);
Ans+=C2(Cnt[0][0])*(n-Cnt[0][0])+C2(Cnt[0][2])*(n-Cnt[0][2])+C2(Cnt[2][0])*(n-Cnt[2][0])+C2(Cnt[2][2])*(n-Cnt[2][2]);
printf("%lld\n",Ans);
}