【洛谷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;
}
posted @ 2021-02-26 23:18  stoorz  阅读(56)  评论(0编辑  收藏  举报