【洛谷P4494】反色游戏
题目
题目链接:https://www.luogu.com.cn/problem/P4494
小\(C\)和小\(G\)经常在一起研究搏弈论问题,有一天他们想到了这样一个游戏.
有一个\(n\)个点\(m\)条边的无向图,初始时每个节点有一个颜色,要么是黑色,要么是白色.现在他们对于每条边做出一次抉择:要么将这条边连接的两个节点都反色(黑变白,白变黑),要么不作处理.他们想把所有节点都变为白色,他们想知道在\(2^m\)种决策中,有多少种方案能达成这个目标.
小\(G\)认为这个问题太水了,于是他还想知道,对于第\(i\)个点,在删去这个点及与它相连的边后,新的答案是多少.
由于答案可能很大,你只需要输出答案对\(10^9+7\)取模后的结果.
\(Q\leq 5,n,m\leq 10^5\)。
思路
考虑对于一个连通块,如果其黑色节点数量为奇数,那么显然无解。否则一定有解,只需要将黑点两两匹配,对于匹配的两个黑点,我们将他们之间的边全部反色(开始全部边设为白色),最后将黑色的边所连接的两个点反过来即可。
考虑对于一个连通块的任意一棵生成树,点两两之间有且仅有一条路径,所以如果这个连通块只是一棵树的话,那么只有一种反色方案。否则设这个连通块的点数为 \(n'\),边数为 \(m'\),剩余 \(m-n+1\) 条边可以任意选择,方案数就是 \(2^{m-n+1}\)。假设有 \(cnt\) 个连通块且每个连通块内黑点数量均为偶数,那么总方案数就是 \(2^{m-n+cnt}\)。
接下来处理删去一个点的操作。设 \(sum\) 为黑点数量为奇数的连通块数量,\(bel_x\) 表示 \(x\) 所在连通块,\(s_i\) 表示连通块 \(i\) 的黑色点奇偶性。显然 \(sum\geq 2\) 无解。否则分类讨论:
- 如果这个点没有任何边连接,也就是一个点是一个连通块,那么如果 \(sum=col_x\),那么答案就是 \(2^{m-n+cnt}\),否则无解。
- 如果这个点不是割点,那么如果 \(col_x\neq s_{bel_x}\),显然无解,否则答案就是 \(2^{m-\text{deg}_x-n+1+cnt}\)。
- 如果这个点是割点,那么如果 \(col_x\neq s_{bel_x}\) 无解,否则枚举在搜索树中 \(x\) 的所有儿子,这些儿子只要有一个不满足子树内黑色节点数量是偶数就无解。如果儿子均满足,显然它父亲那边的子树也是满足的,就不用判断了。如果有解,答案就是 \(2^{m-\text{x}-n+1+cnt-c}\),其中 \(c\) 是 \(x\) 割掉之后形成的树的数量。
时间复杂度 \(O(Q(n+m))\)。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100010,MOD=1e9+7;
int Q,n,m,cnt,sum,tot,head[N],col[N],dfn[N],low[N],bel[N],pw[N],deg[N];
bool flag[N],s[N];
struct edge
{
int next,to,flag;
}e[N*2];
void add(int from,int to)
{
e[++tot]=(edge){head[from],to,0};
head[from]=tot;
}
void prework()
{
memset(head,-1,sizeof(head));
memset(flag,0,sizeof(flag));
memset(deg,0,sizeof(deg));
memset(dfn,0,sizeof(dfn));
memset(s,0,sizeof(s));
tot=1; cnt=sum=0;
}
void tarjan(int x,int rt)
{
dfn[x]=low[x]=++tot;
s[x]=col[x]; bel[x]=rt;
bool mark=0;
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (!dfn[v])
{
tarjan(v,rt);
e[i].flag=e[i^1].flag=1;
low[x]=min(low[x],low[v]); s[x]^=s[v];
if (low[v]>=dfn[x])
{
if (x!=rt || mark) flag[x]=1;
mark=1;
}
}
else low[x]=min(low[x],dfn[v]);
}
}
int main()
{
pw[0]=1;
for (int i=1;i<N;i++)
pw[i]=pw[i-1]*2%MOD;
scanf("%d",&Q);
while (Q--)
{
prework();
scanf("%d%d",&n,&m);
for (int i=1,x,y;i<=m;i++)
{
scanf("%d%d",&x,&y);
add(x,y); add(y,x);
deg[x]++; deg[y]++;
}
for (int i=1;i<=n;i++)
scanf("%1d",&col[i]);
tot=0;
for (int i=1;i<=n;i++)
if (!dfn[i])
{
tarjan(i,i); cnt++;
if (s[i]) sum++;
}
if (sum>=2)
{
for (int i=1;i<=n+1;i++) printf("0 ");
printf("\n"); continue;
}
if (!sum) printf("%d ",pw[m-n+cnt]);
else printf("0 ");
for (int i=1;i<=n;i++)
{
if (sum!=s[bel[i]]) printf("0 ");
else if (!deg[i]) printf("%d ",pw[m-n+cnt]);
else if (!flag[i])
{
if (col[i]!=s[bel[i]]) printf("0 ");
else printf("%d ",pw[m-deg[i]-n+1+cnt]);
}
else
{
int c=0; bool ok=1;
for (int j=head[i];~j;j=e[j].next)
{
int v=e[j].to;
if (low[v]>=dfn[i] && e[j].flag)
{
if (s[v]) { ok=0; break; }
c++;
}
}
if (!ok || s[bel[i]]!=col[i]) printf("0 ");
else printf("%d ",pw[(m-deg[i])-(n-1)+(cnt+c-(i==bel[i]))]);
}
}
printf("\n");
}
return 0;
}