[省选集训2022] 模拟赛7


A

题目描述

给定 \(n\) 个黑白球,排成一个序列。现在要把黑白两种颜色的球消除到只剩一个球,操作步骤是:选取一段长度为奇数的前缀,从后往前取出后三个球,然后根据规则将其变成一个球,循环这个过程直到只剩一个球,然后把它放在序列的最前端。

其中规则由一个长度为 \(8\) 的字符串给出,表示这三个球的颜色从后往前看成 \(01\) 串的状态压缩对应球的颜色,也就是从后往前代表从高位到低位。(这个颜色的名字是薇尔莉特,发现新色号 o(*≧▽≦)ツ)

\(n\leq 10^5\)

解法

这道题不能像 Median Replace 一样直接贪心,因为规则是多样的。

考虑一个神奇的题意转化,既然操作是三元的我们把前两位看成函数,第三位看成自变量,那么整个就是 \(f(x)\) 的效果。那么考虑合并的意思就是 \(f(f(f(...\) 这样多重函数的嵌套,并且我们可以把它理解为一个新的函数 \(h(x)\)

由于函数只有 \(4\) 种:不变、交换、变 \(0\)、变 \(1\);所以我们可以对函数建出 \(\tt DFA\)\(4\times 4\) 的转移可以根据函数的意义写出。考虑判定性问题只需要记录可不可以凑出这个函数,也就是一个大小为 \(4\) 的状态。可以预先处理出规则对应着什么函数,然后一对一对地加入数来转移,就两种方法:直接合并函数,前三位带入求值之后和第四位组合成新函数。

那么需要计数怎么办呢?我们可以 \(dp\)\(dp\),也就是我们记录一个 \(2^4\) 的状态来转移,表示哪些函数是可能被组合出来的。时间复杂度 \(O(n)\)

#include 
#include 
const int M = 100005;
const int MOD = 998244353;
#define rep(i,a,b) for(int i=(a);i<(b);i++)
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int T,n,a[10],t[10][10],dp[10][20],f[20][10],ty[10];
char s[M];
void add(int &x,int y) {x=(x+y)%MOD;}
int mold(int x,int y)
{
	if(x==0 && y==1) return 0;//do not change
	if(x==1 && y==0) return 1;//swap them
	if(x==0 && y==0) return 2;//all zero
	return 3;//all one
}
int ask(int s,int x)//s::f(x)=?
{
	if(s==0) return x;
	if(s==1) return x^1;
	if(s==2) return 0;
	return 1;
}
void work()
{
	memset(f,0,sizeof f);
	memset(dp,0,sizeof dp);
	for(int i=0;i<8;i++) scanf("%1d",&a[i]);
	for(int i=0;i<4;i++) ty[i]=mold(a[i],a[i+4]);
	scanf("%s",s+1),n=strlen(s+1);
	rep(i,0,16) rep(k1,0,2) rep(k2,0,2)
	{
		int s=k1|(k2<<1);
		rep(j,0,4) if(i>>j&1)//merge two function
			f[i][s]|=(1<>j&1)//k1=x --> f(x) 
			f[i][s]|=(1<>k&1)
				pd|=ask(k,i);
			add(ans,pd*dp[w][j]);
		}
	printf("%d\n",ans);
}
signed main()
{
	freopen("game.in","r",stdin);
	freopen("game.out","w",stdout);
	T=read();//initialize
	t[0][0]=0;t[0][1]=1;t[0][2]=2;t[0][3]=3;
	t[1][0]=1;t[1][1]=0;t[1][2]=3;t[1][3]=2;
	t[2][0]=t[2][1]=t[2][2]=t[2][3]=2;
	t[3][0]=t[3][1]=t[3][2]=t[3][3]=3;
	while(T--) work();
}

B

题目描述

\(n\) 个灯构成一棵有根树,定义一个灯的 \(a_i\) 表示 \(i\) 到子树中的最长链,设 \(b_i\) 表示这个灯是开\(/\)关。有 \(m\) 个操作,每次操作之后都需要输出开着灯 \(a_i\) 的异或值:

  • 翻转路径 \((u,v)\) 灯开关的状态,然后更改根为 \(x\)
  • 翻转子树内 \(u\) 灯开关的状态(注意此时的树根),然后更改根为 \(x\)

\(n\leq 2\cdot 10^5\)

解法

最长路径问题都可以向直径方向考虑,考虑以点 \(x\) 为根时 \(x\) 的最长链的端点,一定是直径的端点,并且最长链一定经过直径的中点。设 \(f_x\) 表示以 \(x\) 为根时 \(x\) 的最长链,\(g_x\) 表示次长链,那么以直径的中点 \(rt\) 建树,\(x\) 为根的时候只有 \(rt\)\(x\) 可以取到 \(f_x\),其他点只能取到 \(g_x\)

我们可以维护每个点的开关状态(树剖板子),然后把链的值算上去即可,时间复杂度 \(O(n\log^2 n)\)

细节:直径中点不一定是点,可能是边。那么我们从边的两个端点开始染色即可,每个点都可以找到其对应的根。

#include 
#include 
#include 
#include 
using namespace std;
const int M = 200005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
void write(int x)
{
	if(x>=10) write(x/10);
	putchar(x%10+'0');
}
int n,m,tot,Ind,f[M],dp[M][2],t[M<<2][4],fl[M<<2];
int fa[M],siz[M],dep[M],son[M],dfn[M],top[M],id[M];
int R[3],b[M],zx[M][20];
struct edge{int v,next;}e[M<<1];
int Abs(int x) {return x>0?x:-x;}
//initialize
void dfs1(int u,int p)
{
	siz[u]=1;fa[u]=p;
	dep[u]=dep[p]+1;zx[u][0]=p;
	for(int i=1;i<20;i++)
		zx[u][i]=zx[zx[u][i-1]][i-1];
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(v==p) continue;
		dfs1(v,u);
		siz[u]+=siz[v];
		if(siz[son[u]]>1;
	build(i<<1,l,mid);
	build(i<<1|1,mid+1,r);
	up(i);
}
void work(int i,int l,int r,int L,int R)
{
	if(L>r || l>R) return ;
	if(L<=l && r<=R) {flip(i);return ;}
	int mid=(l+r)>>1;down(i);
	work(i<<1,l,mid,L,R);
	work(i<<1|1,mid+1,r,L,R);
	up(i); 
}
int ask(int i,int l,int r,int L,int R)
{
	if(L>r || l>R) return 0;
	if(L<=l && r<=R) return t[i][0]^t[i][2];
	int mid=(l+r)>>1;down(i);
	return ask(i<<1,l,mid,L,R)^
	ask(i<<1|1,mid+1,r,L,R);
}
//chain-division
void upd(int u,int v)
{
	while(top[u]!=top[v])
	{
		if(dep[top[u]] q;
	b[R[1]]=R[1];b[R[2]]=R[2];
	q.push(R[1]);q.push(R[2]);
	while(!q.empty())
	{
		int u=q.front();q.pop();
		for(int i=f[u];i;i=e[i].next)
			if(!b[e[i].v])
				b[e[i].v]=b[u],q.push(e[i].v);
	}
}
int jump(int x,int y)
{
	for(int i=19;i>=0;i--)
		if(y>>i&1) x=zx[x][i];
	return x;
}
signed main()
{
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	n=read();m=read();
	for(int i=1;iur)
				work(1,1,n,dfn[u],ur);
			else
			{
				int v=jump(rt,dep[rt]-dep[u]-1);
				work(1,1,n,1,dfn[v]-1);
				work(1,1,n,dfn[v]+siz[v],n);
			}
		}
		rt=read();
		write(t[1][2]^get(rt,b[rt])),puts("");
	}
}

C

题目描述

给你一张 \(n\) 个点 \(m\) 条边的图,对于一个大小至少为 \(2\) 的点集 \(S\),定义 \(f(S)=\sum_{(u,v)\in E}\frac{[u,v\in S]}{|S|-1}\),请你判断是否存在 \(f(S)>lim\)

\(n\leq 300,m\leq 10^3,lim\leq 70\)

解法

在子任务的环境下模拟退火竟然跑了 \(55\) 分,我已经很满足了。

我们可以枚举一个点 \(x\) 强制选他,那么剩下的直接跑最大权闭合子图即可。

不想跑 \(n\) 遍网络流怎么办?在图变化的时候把待删去边的流量退掉即可。

#include 
#include 
#include 
using namespace std;
const int M = 10005;
const int inf = 0x3f3f3f3f;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,k,S,T,ans,tot,f[M],cur[M],d[M],F[M];
struct edge{int v,c,next;}e[M<<5];
void add(int u,int v,int c)
{
	e[++tot]=edge{v,c,f[u]},f[u]=tot;
	e[++tot]=edge{u,0,f[v]},f[v]=tot;
}
int bfs()
{
	queue q;q.push(S);d[S]=1;
	for(int i=1;i<=T;i++) d[i]=0;
	while(!q.empty())
	{
		int u=q.front();q.pop();
		if(u==T) return 1;
		for(int i=f[u];i;i=e[i].next)
		{
			int v=e[i].v;
			if(!d[v] && e[i].c>0)
			{
				d[v]=d[u]+1;
				q.push(v);
			}
		}
	}
	return 0;
}
int dfs(int u,int ept)
{
	if(u==T) return ept;
	int tmp=0,flow=0;
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(d[v]==d[u]+1 && e[i].c>0)
		{
			tmp=dfs(v,min(ept,e[i].c));
			if(!tmp) continue;
			ept-=tmp;flow+=tmp;
			e[i].c-=tmp;e[i^1].c+=tmp;
			if(!ept) break;
		}
	}
	return flow;
}
signed main()
{
	freopen("socialbutterfly.in","r",stdin);
	freopen("socialbutterfly.out","w",stdout);
	n=read();m=read();k=read();
	S=0;T=n+m+1;tot=1;
	for(int i=1;i<=m;i++)
	{
		int u=read(),v=read();
		add(S,i,1);
		add(i,u+m,inf);
		add(i,v+m,inf);
	}
	int tmp=tot;
	for(int i=0;i<=T;i++) F[i]=f[i];
	for(int b=1;b<=n;b++)
	{
		tot=tmp;ans=m;
		for(int i=2;i<=tot;i+=2)
			e[i].c+=e[i^1].c,e[i^1].c=0;
		for(int i=0;i<=T;i++) f[i]=F[i];
		for(int i=1;i<=n;i++)
			if(i!=b) add(i+m,T,k);
		while(bfs())
		{
			for(int i=0;i<=T;i++) cur[i]=f[i];
			ans-=dfs(S,inf);
		}
		if(ans>0) {puts("Yes");return 0;}
	}
	puts("No");
}