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\)都是单调增加的,可以用动态点分治维护一个滑动窗口实现。

​ 好毒瘤蛙。。。

退役老年选手是写不出来点分治的。。。
posted @ 2020-12-27 09:39  tkys_AUSTIN  阅读(90)  评论(0编辑  收藏  举报