[HAOI2018] 反色游戏

一、题目

点此看题

二、解法

可以根据每个点列出 \(n\) 个异或方程,其中变元是 \(m\) 条边,答案是解的个数。

但是我们把问题泛化得太多了,还是很难做。对于每个连通块分别考虑,从简单情形开始思考,我们不妨先思考树怎么做?如果黑点个数为奇数个,那么一定无解;否则可以从叶子开始构造出一组唯一解。

树的情形带给我们很大的启发。如果连通块中黑点个数为奇数同样无解,否则可以把非树边当成自由变元,先确定非树边的取值之后,通过树边可以得到唯一解。设连通块数是 \(p\),答案是 \(2^{m-n+p}\)

现在还要支持删点,本题只需要描述清楚连通块的变化就可以计算答案。考虑 \(\tt tarjan\) 求点双连通分量的过程,可以知道删去一个点后会分裂成多少个连通块,我们还要记录子树的黑点个数,这样就可以方便地判断分裂出来的连通块中是否存在奇数个黑点,一遍 \(\tt tarjan\) 就可以处理出所有信息,时间复杂度 \(O(n)\)

#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
const int M = 100005;
const int MOD = 1e9+7;
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,m,dfn[M],low[M],fl[M],siz[M],deg[M];
int rt,cnt,pw[M],cut[M],bel[M],sub[M];
char s[M];vector<int> g[M];
void tarjan(int u,int fa)
{
	dfn[u]=low[u]=++cnt;bel[u]=rt;
	fl[u]=1;siz[u]=(s[u]=='1');
	for(int v:g[u])
	{
		if(!dfn[v])
		{
			tarjan(v,u);
			low[u]=min(low[u],low[v]);
			siz[u]+=siz[v];
			if(low[v]>=dfn[u])
			{
				cut[u]++;sub[u]+=siz[v];
				fl[u]&=(siz[v]%2==0);
			}
		}
		else if(v^fa)
			low[u]=min(low[u],dfn[v]);
	}
	if(!fa) cut[u]--;
}
void work()
{
	n=read();m=read();cnt=0;
	for(int i=1;i<=n;i++)
	{
		dfn[i]=low[i]=fl[i]=sub[i]=0;
		siz[i]=deg[i]=cut[i]=bel[i]=0;
		g[i].clear();
	}
	for(int i=1;i<=m;i++)
	{
		int u=read(),v=read();
		g[u].push_back(v);
		g[v].push_back(u);
		deg[u]++;deg[v]++;
	}
	scanf("%s",s+1);
	int d=m-n,sum=0;
	for(int i=1;i<=n;i++) if(!dfn[i])
	{
		rt=i;tarjan(i,0);
		d++;sum+=(siz[i]&1);
	}
	printf("%d ",sum?0:pw[d]);
	for(int i=1;i<=n;i++)
	{
		if(fl[i] && (siz[bel[i]]-sub[i]-(s[i]=='1'))%2==0
		&& sum-(siz[bel[i]]%2)==0)
			printf("%d ",pw[d-deg[i]+cut[i]+1]);
		else printf("0 ");
	}
	puts("");
}
signed main()
{
	T=read();pw[0]=1;
	for(int i=1;i<M;i++)
		pw[i]=pw[i-1]*2ll%MOD;
	while(T--) work();
}
posted @ 2022-08-11 20:25  C202044zxy  阅读(181)  评论(0编辑  收藏  举报