A.Row GCD
(A,B)数组的长度分别为(n,m),对于(k=1 o n)求(GCD(a_1+b_k,a_2+b_k,dots,a_n+b_k))
(1le n,mle2cdot10^5),(1le a_i,b_ile10^18)
solution
注意到:(GCD(a_1+b_k,a_2+b_k,dots,a_n+b_k)=GCD(a_1+b_k,a_2-a_1,dots,a_n-a_{n-1}))
预处理(GCD(a_1+b_k,a_2-a_1,dots,a_n-a_{n-1}))
时间复杂度:(O((N+M)logA))
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=200010;
int n,m;
ll a1,ga,ans;
ll gcd(ll a,ll b){return !b?a:gcd(b,a%b);}
int main(){
//freopen("a.in","r",stdin);
//freopen("a.out","w",stdout);
scanf("%d%d",&n,&m);
scanf("%lld",&a1);
for(int i=2;i<=n;++i){
ll x;scanf("%lld",&x);
ga=gcd(abs(x-a1),ga);
}
for(int i=1;i<=m;++i){
ll x;scanf("%lld",&x);
ans=gcd(a1+x,ga);
printf("%lld ",ans);
}
return 0;
}
B. Glass Half Spilled
有(n)个杯子,每个杯子有容量(a_i)和水量(b_i)。一次操作为((i,j,x))表示把杯子(i)中的(x(xle b_i))倒入杯子(j)中,但是杯子(j)只能得到一半((frac{x}{2}))的水并且总水量不能超过(a_j) 。你可以进行任意操作,询问当最后可以剩下(k=1 o n)个杯子时,这(k)个杯子中的水量的和的最大值。
(1le nle100,0le b_ile a_ile 100)
solution
显然不选的杯子的水一定会被倒完,并且不管分多少次倒都有一半会浪费。
假设最后选的杯子集合为(S),(W)为所有(n)个杯子的水量之和,那么就是最大化$Min{sum_{iin S}a_i,frac{W}{2}+frac{1}{2}sum_{i in S}b_i } $
枚举这两个式子的差值,只需要计算(Max { sum_{i in S} a_i })
(f_{n,k,x})表示前(n)个杯子选(k)个差值为(x)的最大值,dp即可
记(M=2sum_{i=1}^{n} max { a_i,b_i })时间复杂度就是:(O(N^2M))
#include<bits/stdc++.h>
using namespace std;
const int N=110,M=40010,inf=1e9;
int n,a[N],b[N],mx,f[2][N][M],W,cur=0;
void upd(int&x,int y){if(x<y)x=y;}
int main(){
//freopen("b.in","r",stdin);
//freopen("b.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d%d",&a[i],&b[i]);
mx+=max(2*a[i],b[i]);W+=b[i];
}
for(int i=0;i<=n;++i)
for(int j=-mx;j<=mx;++j)f[cur][i][j+mx]=-inf;
f[cur][0][0+mx]=0;
for(int i=1;i<=n;++i){
int t1=a[i],t2=b[i]-2*a[i];
for(int j=0;j<=n;++j)
for(int k=-mx;k<=mx;++k){
f[cur^1][j][k+mx]=f[cur][j][k+mx];
}
for(int j=0;j<n;++j)
for(int k=-mx;k<=mx;++k){
if(k+mx<-mx||k+mx>mx)continue;
upd(f[cur^1][j+1][k+t2+mx],f[cur][j][k+mx]+t1);
}
cur^=1;
}
for(int i=1;i<=n;++i){
int ans=-inf;
for(int j=-mx;j<=mx;++j)
if(j+W>0)upd(ans,2*f[cur][i][j+mx]);
else upd(ans,2*f[cur][i][j+mx]+j+W);
printf("%.10lf ",1.0*ans/2);
}
return 0;
}
C. Latin Square
给定一个(n imes n)的二维数组,满足每行每列都是一个排列。
一次操作为:对所有行向右/左平移,对所有列向上/下平移,对所有行/列进行置换
输出(m)次操作后最终的矩阵
(1 le n le 10^3 \, 1 le m le 10^5)
solution
维护((x,y,z))表示$a[x][y]=z $
时间复杂度:(O(n^2+m))
#include<bits/stdc++.h>
using namespace std;
const int N=1010,M=100010;
int T,n,m,a[N][N],b[3],c[3],Ans[N][N];
char s[M];
void dec(int&x){if(--x<1)x+=n;}
void inc(int&x){if(++x>n)x-=n;}
int main(){
//freopen("C.in","r",stdin);
//freopen("C.out","w",stdout);
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)scanf("%d",&a[i][j]);
scanf("%s",s+1);
for(int i=0;i<3;++i)b[i]=i,c[i]=0;
for(int i=1;i<=m;++i){
switch(s[i]){
case 'L':dec(c[b[1]]);break;
case 'R':inc(c[b[1]]);break;
case 'U':dec(c[b[0]]);break;
case 'D':inc(c[b[0]]);break;
case 'I':swap(b[1],b[2]);break;
case 'C':swap(b[0],b[2]);break;
}
}
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j){
int x=(((!b[0])?i:(b[0]&1)?j:a[i][j])+c[b[0]]-1)%n+1;
int y=(((!b[1])?i:(b[1]&1)?j:a[i][j])+c[b[1]]-1)%n+1;
int z=(((!b[2])?i:(b[2]&1)?j:a[i][j])+c[b[2]]-1)%n+1;
Ans[x][y]=z;
}
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j)printf("%d ",Ans[i][j]);
printf("
");
}
puts("");
}
return 0;
}
D.Flip and Reverse
给出一个长度为(n)的串(S),一次操作可以选择一个(01)个数相同的区间,将区间取反再反转,求给出字符串经过任意操作后能够得到的字典序最小的串。
(1 le n le 2cdot10^5)
solution
这种题的一般套路是找到某种工具来刻画给出的操作
记起点为(x=0),从左到右扫描,字符(1)就是(x o x+1), 字符(0)就是(x o x-1) ,那么最终(S)是一个从(0)到(t)的一条特定的欧拉路径,其中(t)为最后的(x)值
一次操作其实就是将一个环反向,可以证明允许随意操作就是等价于所有的欧拉路径都是合法的。最小的串就是要求带边权的最小字典序的欧拉路径,直接dfs即可。
时间复杂度:(O(n))
#include<bits/stdc++.h>
using namespace std;
const int N=1000010;
int T,n,L[N],R[N],t,tot;
char s[N],ans[N];
void dfs(int x){
while(L[x+n])L[x+n]--,dfs(x-1),ans[++tot]='0';
while(R[x+n])R[x+n]--,dfs(x+1),ans[++tot]='1';
}
int main(){
//freopen("d.in","r",stdin);
//freopen("d.out","w",stdout);
scanf("%d",&T);
while(T--){
scanf("%s",s+1);
n=strlen(s+1);t=tot=0;
for(int i=-n;i<=n;++i)L[i+n]=R[i+n]=0;
for(int i=1;i<=n;++i){
if(s[i]=='1')R[t+n]++,t++;
else L[t+n]++,t--;
}
dfs(0);
reverse(ans+1,ans+tot+1);
for(int i=1;i<=tot;++i)putchar(ans[i]);
puts("");
}
return 0;
}
E. Nim Shortcuts
二维的(nim)游戏,增加了(n)个shortcut点((x_i,y_i)) ,规定这些点是必败的。给出(m)个初始状态((a_i,b_i)) ,询问是否有必胜策略。
$1 le n,m le 10^5 , 1 le a_i,b_i,x_i,y_i le 10^9 $
solution
将状态转化成一个二维平面,显然shortcut点是必败的。而当一个点被确定为必败的,那么它右方和上方的非shortcut点都是必胜的;一个非shortcut是必败的当且仅后继中没有一个点是必胜的。
我们通过下面的操作来找出所有的非shortcut的必败点,从((0,0))出发,假设当前在((x,y)),依次进行下列判断:
- 1 ((x,y))是一个shortcut点,移动到((x+1,y+1))
- 2 ((x,y))的左方有一个shortcut点,移动到((x,y+1))
- 3 ((x,y))的下方有一个shortcut点,移动到((x+1,y))
- 4 123均不满足,则((x,y))是一个非shortcut的必败点,移动到((x+1,y+1))
最后所有的必败点就是shortcut点和非shortcut的必败点,询问直接用map查询,但是坐标的范围很大
注意到中间的很多点是可以直接跳过的,可以找到shortcut点中当前横坐标(x)的后继(X)和纵坐标(y)的后继(Y),取(Delta =min{X-x,Y-y}) 记录((x,y, Delta))表示((x,y),(x+1,y+1),dots,(x+Delta-1,y+Delta-1))的点都是必败的,然后直接跳到((x+Delta,y+Delta)) 查询同样用map
时间复杂度:(O((n+m)log n ))
我太懒了没有写~~~
F. Range Diameter Sum
给出一颗(n)个点的树和一个关于点的排列,求所有连续区间点集的直径和。
$1 le n le 10^5 $
solution
对于所有边((u,v)),新建一个点(w),将一条边拆成两条边((u,w)(w,v)) ,记原来的点为实点,新加的点为虚点。记树上的一个圆(C(v,r))表示到(v)的树上距离不超过(r)的点的集合。拆边之后,对于一个实点集合(S),一定能够找到唯一的((v,r)),满足(r)最小,$ S subseteq C(v,r)$。(进一步可以证明 (v) 是 (S) 任一直径的中点, (r) 是直径的 (frac{1}{2}) )
类似几何中的圆,合并两个集合(C(v_1,r_1))和(C(v_2,r_2)) 有下面三种情况:
- 1 (dis(v_1,v_2)+r_2le r_1),则$C(v_1,r_1)cup C(v_2,r_2) = C(v_1,r_1) $
- 2 (dis(v_1,v_2)+r_1le r_2),则(C(v_1,r_1)cup C(v_2,r_2) = C(v_2,r_2))
- 3 不满足1 2 ,如果新的集合为(C(v^{'},r^{'})),那么(r^{'}=frac{1}{2}(dis(v_1,v_2)+r_1+r_2)) ,(v^{'})是(v_1 o v_2)路径上到(v_1)距离为(frac{1}{2}(dis(v_1,v_2)-r_1+r_2))的点
接下来考虑分治解决原问题:
设当前区间为([l,r]),(m=frac{1}{2}(l+r)) ,记(Cl_{i})为覆盖实点集合((i,i+1,dots,m)) 的最小圆,(Cr_{j})为覆盖实点集合((m+1,m+2,cdots,j)) 的最小圆,枚举(i=m o l),考虑如何计算所有的(j=m+1 o r)的答案。
对于(Cl_i(v_i,r_i))和(Cr_j(v_j,r_j)),(C^{'}=Cl_{i} cup Cr_{j}) ,只需要计算(r')即可,而由于(j)是不断变大的,所以会存在一个区间([L,R]),区间左边满足情况1,区间后边满足情况2,区间满足情况3。现在就只需要考虑计算3的$ sum_{j=L}^{R} dis(v_i,v_j) $ 的值,因为(i)是不断减小的,以为着(L,R)都是单调增加的,可以用动态点分治维护一个滑动窗口实现。
好毒瘤蛙。。。
退役老年选手是写不出来点分治的。。。