[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();
}