2023.8.24

A

Breeding Bugs

保留 \(\{a_n\}\) 中的一些数,使得没有两个数的和为质数。输出集合最终的最大大小。

\(T\le 4\)\(n\le 750\)\(a_i\le 10^9\).

不妨将相同的 \(a_i\) 合并,另外地,\(1\) 至多只能有 \(1\) 个。

这时发现会有若干对数不能共存,考虑建模。

将源点向奇数连边,反之偶数向汇点连边,流量为 \(cnt_i\),有限制的 \(i\)\(j\) 之间由奇数向偶数连一条 inf 的边,跑最小割即可。

使用 Miller_Rabin 可以用 \(O(n^2\sqrt[4]{V})\) 的时间求出限制关系。

实际上只需以 \(2,3,5,7,11\) 为底数就能解决 \(\rm 2e9\) 以内的素性检验。

非常好火车头,长 2.1k.

#include<bits/stdc++.h>
#define N 755
#define M 1200000
#define inf 1e9
#define Test 10
using namespace std;
int read(){
	int x=0,w=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*w;
}
int qpow(int k,int b,int p){
	int ret=1;
	while(b){
		if(b&1)ret=1ll*ret*k%p;
		k=1ll*k*k%p,b>>=1;
	}
	return ret;
}
bool Miller_Rabin(int n){
	if(n==2)return true;
	if(n<2||!(n&1))return false;
	for(int i=1,a;i<=Test;i++){
		a=rand()%(n-2)+2;
		if(qpow(a,n-1,n)!=1)return false;
	}
	return true;
}
int Case,n,a[N],b[N],cnt[N];
int head[N],ver[M],nxt[M],edge[M],tot;
void add(int u,int v,int w){
	nxt[++tot]=head[u];
	ver[tot]=v,edge[tot]=w;
	head[u]=tot;
}
int d[N],now[N],flow,S,T;
bool bfs(){
	memset(d,0,sizeof(d));
	queue<int>q;
	q.push(S),d[S]=1,now[S]=head[S];
	while(!q.empty()){
		int x=q.front();q.pop();
		for(int i=head[x],y;i;i=nxt[i]){
			if(edge[i]&&!d[y=ver[i]]){
				now[y]=head[y];
				q.push(y),d[y]=d[x]+1;
				if(y==T)return true;
			}
		}
	}
	return false;
}
int dinic(int x,int flow){
	if(x==T)return flow;
	int ret=0;
	for(int i=now[x],y;i;i=nxt[i]){
		now[x]=i,y=ver[i];
		if(edge[i]&&d[y]==d[x]+1){
			int k=dinic(y,min(flow,edge[i]));
			if(!k)d[y]=0;
			edge[i]-=k,edge[i^1]+=k;
			ret+=k,flow-=k;
		}
	}
	return ret;
}
int main(){
	srand(233);
	Case=read();
	while(Case--){
		n=read(),memset(head,0,sizeof(head));
		memset(cnt,0,sizeof(cnt));
		tot=1,S=n+1,T=S+1;
		for(int i=1;i<=n;i++)a[i]=read();
		sort(a+1,a+1+n);
		int st=1;
		while(a[st]==1&&a[st+1]==1)st++;
		for(int i=1;i<=n;i++)b[i]=a[i];
		int len=unique(a+1,a+1+n)-a-1;
		for(int i=st;i<=n;i++)
			cnt[lower_bound(a+1,a+1+len,b[i])-a]++;
		for(int i=1;i<=len;i++){
			if(a[i]&1)add(S,i,cnt[i]),add(i,S,0);
			else add(i,T,cnt[i]),add(T,i,0);
		}
		for(int i=1;i<len;i++)
			for(int j=i+1;j<=len;j++){
				if(Miller_Rabin(a[i]+a[j])){
					if(a[i]&1)add(i,j,inf),add(j,i,0);
					else add(j,i,inf),add(i,j,0);
				}
			}
		int ans=0;
		while(bfs()){
			while(flow=dinic(S,inf))
				ans+=flow;
		}
		printf("%d\n",(n-st+1)-ans);
	}
	
	return 0;
}

B

\(n\times m\) 的网格上有 \(k\) 个炸弹和 \(b\) 个水晶。

你可以引爆一个炸弹,可以决定它炸行或列。对被连锁的炸弹继续执行操作。

问被炸弹覆盖的水晶的最大个数。

\(n,m\le 3000\)\(k+b\le nm\).

被连锁的炸弹显然应朝被炸的方向的另一边炸。

将每个炸弹朝其上下左右的第一个炸弹连边,形成若干连通块。

现在单独对每个块考虑。

若连通块中有环,那么引爆环中的一个可以把连通块涉及的所有行列引爆。

若没有环,最优解一定为一个左右/上下没有炸弹的炸弹,此时只会损失某行或某列的水晶,减去这个值。

时间复杂度 \(O(nm\cdot\alpha(nm))\).

附上 2.8k 的抽象 std。

#include<bits/stdc++.h>
#define N 3010
#define pb push_back
using namespace std;
int read(){
	int x=0,w=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*w;
}
int gets(){
	char ch=getchar();
	while(ch!='k'&&ch!='b'&&ch!='.')ch=getchar();
	return ch=='k'?1:(ch=='b'?2:0);
}
int n,m,s[N][N];
int mp[N][N];
int A[N][N],la[N],B[N][N],lb[N];
int f[N*N],tot[N*N],c[N],cnt[N][N];
bool vis[N*N],flag[N*N],vx[N],vy[N];
int id(int x,int y){
	return (x-1)*m+y;
}
int find(int x){
	return f[x]==x?x:f[x]=find(f[x]);
}
void merge(int x,int y){
	x=find(x),y=find(y);
	if(x!=y)f[x]=y,flag[y]|=flag[x];
	else flag[x]=true;
}
int qry(int l1,int r1,int l2,int r2){
	return s[l2][r2]-s[l1-1][r2]-s[l2][r1-1]+s[l1-1][r1-1];
}
int ans;
void solve(int l,int r){
	int ret=0;
	for(int i=l,x,y;i<=r;i++){
		x=(c[i]+m-1)/m,y=c[i]-(x-1)*m;
		if(!vx[x]){
			vx[x]=true;
			for(int j=1;j<=la[x];j++){
				if(!cnt[x][A[x][j]])ret++;
				cnt[x][A[x][j]]++;
			}
		}
		if(!vy[y]){
			vy[y]=true;
			for(int j=1;j<=lb[y];j++){
				if(!cnt[B[y][j]][y])ret++;
				cnt[B[y][j]][y]++;
			}
		}
	}
	if(flag[find(c[l])])ans=max(ans,ret);
	else{
		for(int i=l,x,y,tp;i<=r;i++){
			x=(c[i]+m-1)/m,y=c[i]-(x-1)*m;
			if(qry(x,1,x,m)==1){
				tp=ret;
				for(int j=1;j<=la[x];j++)
					tp-=(cnt[x][A[x][j]]==1);
				ans=max(ans,tp);
			}
			if(qry(1,y,n,y)==1){
				tp=ret;
				for(int j=1;j<=lb[y];j++)
					tp-=(cnt[B[y][j]][y]==1);
				ans=max(ans,tp);
			}
		}
	}
	for(int i=l,x,y;i<=r;i++){
		x=(c[i]+m-1)/m,y=c[i]-(x-1)*m;
		if(vx[x]){
			vx[x]=false;
			for(int j=1;j<=la[x];j++)cnt[x][A[x][j]]=0;
		}
		if(vy[y]){
			vy[y]=false;
			for(int j=1;j<=lb[y];j++)cnt[B[y][j]][y]=0;
		}
	}
}
int main(){
	n=read(),m=read(),read(),read();
	for(int i=1;i<=n*m;i++)f[i]=i;
	for(int i=1,opt;i<=n;i++)
		for(int j=1;j<=m;j++){
			mp[i][j]=opt=gets();
			if(opt==2)s[i][j]=1;
			if(opt==1)
				A[i][++la[i]]=j,B[j][++lb[j]]=i;
			s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
		}
	for(int i=1;i<=n;i++){
		int lst=-1;
		for(int j=1;j<=m;j++)
			if(mp[i][j]==2){
				if(lst!=-1)merge(id(i,lst),id(i,j));
				lst=j;
			}
	}
	for(int j=1;j<=m;j++){
		int lst=-1;
		for(int i=1;i<=n;i++)
			if(mp[i][j]==2){
				if(lst!=-1)merge(id(lst,j),id(i,j));
				lst=i;
			}
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			if(mp[i][j]==2)tot[find(id(i,j))]++;
	for(int i=1;i<=n*m;i++)tot[i]+=tot[i-1];
	int sum=tot[n*m];
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			if(mp[i][j]==2)c[tot[find(id(i,j))]--]=id(i,j);
	int lst=0;
	for(int i=1;i<=sum+1;i++)
		if(i==sum+1||find(c[i])!=find(c[i-1]))
			solve(lst+1,i-1),lst=i-1;
	printf("%d\n",ans);
	
	return 0;
}

C

\(n\times m\) 的棋盘上有若干棋子,两人移动棋子,规则如下:

  • 棋子不能超出棋盘

  • 棋子不能走之前走过的点和障碍

  • 棋子可以向左、向右、向下走

  • 棋子可以从 \((i,1)\) 走到 \((i,m)\),从 \((i,m)\) 走到 \((i,1)\)

无法行动的玩家判负。问谁最终获胜。

注意每个棋子完全独立。

\(n,m\le 1000\)\(\sum n,\sum m\le 4000\).

赛时眼瞎忘了可以往左走丢了 40 分。

考虑将每个棋子初始状态的 SG 函数异或起来。

容易写出 \(O(nm^2)\) 的转移,注意倒序递推。

每行至少有一个障碍

棋子只有三种状态:

  • 刚从上一行下来,可以往左右走

  • 右边走过了,只能往左走

  • 左边走过了,只能往右走

无需记录经过区间,状态数 \(O(nm)\).

无障碍格子

SG 函数可能需要打表找规律。

一般情况

我们需要一种处理无障碍格子的行的方法。

若来到 \((x,y)\),向左走,那么 \((x,y)\) 变为包含障碍的格子,转化为包含障碍格子的情况。

考虑怎么计算 SG 值。

\((x,y-1)\) 的格子只能向左或向下走,那么只需求出 \((x,y-2)\) 的 SG 值。

一路递推发现我们要求出 \((x,y+1)\) 的 SG 值,此时只能向下走,那么可以求出该 SG 值并回推。

显然的是 SG 值只能为 \(0,1,2,3\),考虑预处理以下信息:

  • \((x,y)\) 的 SG 值为 \(v\) 时,一路向左/右推,位置 \((x,1)\)\((x,m)\) 的 SG 值为多少。

  • \((x,1)\)\((x,m)\) 的 SG 值为 \(v\) 时,一路向左/右推,到 \((x,y)\) 时的 SG 值为多少。

这样就可以 \(O(1)\) 计算每个位置的 SG 值。

时间复杂度 \(O(nm)\).

std 达到了优秀的 3k 而且完全看不懂。不知道有没有避免屎山代码的方法。

#include<bits/stdc++.h>
#define N 1005
using namespace std;
int read(){
	int x=0,w=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*w;
}
int n,m,sg[3][N][N];
int a[4][N],b[4][N],c[4][N],d[4][N];
char mp[N][N];
int SG(int o,int x,int y){
	if(sg[o][x][y]!=-1)return sg[o][x][y];
	bool v[4];v[0]=v[1]=v[2]=v[3]=false;
	if(x<n&&mp[x+1][y]!='#')
		v[SG(0,x+1,y)]=true;
	int L=(y!=1)?y-1:m,R=(y!=m)?y+1:1;
	if(o!=2&&mp[x][L]!='#'&&m!=1)
		v[SG(1,x,L)]=true;
	if(o!=1&&mp[x][R]!='#'&&m!=1)
		v[SG(2,x,R)]=true;
	for(int i=0;;i++)
		if(!v[i])return sg[o][x][y]=i;
	return 10086;
}
void solve(int o){
	bool v[4];
	for(int u=0;u<4;u++){
		int lst=u;
		for(int i=1;i<=m;i++){
			v[0]=v[1]=v[2]=v[3]=false;
			v[lst]=true;
			if(o<n&&mp[o+1][i]!='#')
				v[SG(0,o+1,i)]=true;
			for(int j=0;;j++)
				if(!v[j]){lst=j;break;}
			a[u][i]=lst;
		}
	}
	for(int u=0;u<4;u++)b[u][m]=u;
	for(int i=m-1;i;i--)
		for(int u=0;u<4;u++){
			v[0]=v[1]=v[2]=v[3]=false;
			v[u]=true;
			if(o<n&&mp[o+1][i+1]!='#')
				v[SG(0,o+1,i+1)]=true;
			for(int j=0;;j++)
				if(!v[j]){b[u][i]=b[j][i+1];break;}
		}
	for(int u=0;u<4;u++){
		int lst=u;
		for(int i=m;i;i--){
			v[0]=v[1]=v[2]=v[3]=false;
			v[lst]=true;
			if(o<n&&mp[o+1][i]!='#')
				v[SG(0,o+1,i)]=true;
			for(int j=0;;j++)
				if(!v[j]){lst=j;break;}
			c[u][i]=lst;
		}
	}
	for(int u=0;u<4;u++)d[u][1]=u;
	for(int i=2;i<=m;i++)
		for(int u=0;u<4;u++){
			v[0]=v[1]=v[2]=v[3]=false;
			v[u]=true;
			if(o<n&&mp[o+1][i-1]!='#')
				v[SG(0,o+1,i-1)]=true;
			for(int j=0;;j++)
				if(!v[j]){d[u][i]=d[j][i-1];break;}
		}
	for(int i=1;i<=m;i++){
		v[0]=v[1]=v[2]=v[3]=false;
		if(o<n&&mp[o+1][i]!='#')
			v[SG(0,o+1,i)]=true;
		if(m!=1){
			if(i==1){
				int t=(o<n&&mp[o+1][m]!='#')?SG(0,o+1,m):1;
				v[c[t][2]]=true;
				t=(o<n&&mp[o+1][2]!='#')?(!SG(0,o+1,2)):0;
				v[b[t][2]]=true;
			}
			else if(i==m){
				int t=(o<n&&mp[o+1][1]!='#')?SG(0,o+1,1):1;
				v[a[t][m-1]]=true;
				t=(o<n&&mp[o+1][m-1]!='#')?(!SG(0,o+1,m-1)):0;
				v[d[t][m-1]]=true;
			}
			else{
				int t=(o<n&&mp[o+1][i+1]!='#')?(!SG(0,o+1,i+1)):0;
				v[a[b[t][i+1]][i-1]]=true;
				t=(o<n&&mp[o+1][i-1]!='#')?(!SG(0,o+1,i-1)):0;
				v[c[d[t][i-1]][i+1]]=true;
			}
		}
		for(int j=0;;j++)
			if(!v[j]){sg[0][o][i]=j;break;}
	}
}
int main(){
	int T=read();
	while(T--){
		n=read(),m=read();
		for(int k=0;k<3;k++)
			for(int i=0;i<=n+1;i++)
				for(int j=0;j<=m+1;j++)sg[k][i][j]=-1;
		for(int k=0;k<4;k++)
			for(int i=0;i<=max(n,m)+1;i++)
				a[k][i]=b[k][i]=c[k][i]=d[k][i]=0;
		for(int i=1;i<=n;i++)scanf("%s",mp[i]+1);
		int ans=0;
		for(int i=n;i;i--){
			int cnt=0;
			for(int j=1;j<=m;j++)cnt+=(mp[i][j]=='#');
			if(!cnt)solve(i);
			for(int j=1;j<=m;j++)
				if(mp[i][j]=='B')ans^=SG(0,i,j);
		}
		puts(ans?"A":"B");
	}
	
	return 0;
}

D

给出一棵树,对于每个点 \(i\),求出

\[\sum_{j=1}^{n}[\operatorname{dis}(j,i)\le 2\operatorname{dis}(1,i)]d_j \]

\(n\le 2\times 10^6\)\(0\le d_i\le \sum d_i<2^{31}\).

长剖。我不会。

posted @ 2023-08-24 16:42  SError  阅读(19)  评论(0编辑  收藏  举报