cf contest 1458
A.Row GCD
\(A,B\)数组的长度分别为\(n,m\),对于\(k=1\to n\)求\(GCD(a_1+b_k,a_2+b_k,\dots,a_n+b_k)\)
\(1\le n,m\le2\cdot10^5\),\(1\le a_i,b_i\le10^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(x\le b_i)\)倒入杯子\(j\)中,但是杯子\(j\)只能得到一半(\(\frac{x}{2}\))的水并且总水量不能超过\(a_j\) 。你可以进行任意操作,询问当最后可以剩下\(k=1\to n\)个杯子时,这\(k\)个杯子中的水量的和的最大值。
\(1\le n\le100,0\le b_i\le a_i\le 100\)
solution
显然不选的杯子的水一定会被倒完,并且不管分多少次倒都有一半会浪费。
假设最后选的杯子集合为\(S\),\(W\)为所有\(n\)个杯子的水量之和,那么就是最大化$Min{\sum_{i\in 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=2\sum_{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 \times 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("\n");
}
puts("");
}
return 0;
}
D.Flip and Reverse
给出一个长度为\(n\)的串\(S\),一次操作可以选择一个\(01\)个数相同的区间,将区间取反再反转,求给出字符串经过任意操作后能够得到的字典序最小的串。
\(1 \le n \le 2\cdot10^5\)
solution
这种题的一般套路是找到某种工具来刻画给出的操作
记起点为\(x=0\),从左到右扫描,字符\(1\)就是\(x \to x+1\), 字符\(0\)就是\(x \to 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_2\le 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_1\le 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 \to 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 \to l\),考虑如何计算所有的\(j=m+1 \to 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\)都是单调增加的,可以用动态点分治维护一个滑动窗口实现。
好毒瘤蛙。。。
退役老年选手是写不出来点分治的。。。