zoukankan      html  css  js  c++  java
  • 【洛谷P4494】反色游戏

    题目

    题目链接:https://www.luogu.com.cn/problem/P4494
    (C)和小(G)经常在一起研究搏弈论问题,有一天他们想到了这样一个游戏.
    有一个(n)个点(m)条边的无向图,初始时每个节点有一个颜色,要么是黑色,要么是白色.现在他们对于每条边做出一次抉择:要么将这条边连接的两个节点都反色(黑变白,白变黑),要么不作处理.他们想把所有节点都变为白色,他们想知道在(2^m)种决策中,有多少种方案能达成这个目标.
    (G)认为这个问题太水了,于是他还想知道,对于第(i)个点,在删去这个点及与它相连的边后,新的答案是多少.
    由于答案可能很大,你只需要输出答案对(10^9+7)取模后的结果.
    (Qleq 5,n,mleq 10^5)

    思路

    考虑对于一个连通块,如果其黑色节点数量为奇数,那么显然无解。否则一定有解,只需要将黑点两两匹配,对于匹配的两个黑点,我们将他们之间的边全部反色(开始全部边设为白色),最后将黑色的边所连接的两个点反过来即可。
    考虑对于一个连通块的任意一棵生成树,点两两之间有且仅有一条路径,所以如果这个连通块只是一棵树的话,那么只有一种反色方案。否则设这个连通块的点数为 (n'),边数为 (m'),剩余 (m-n+1) 条边可以任意选择,方案数就是 (2^{m-n+1})。假设有 (cnt) 个连通块且每个连通块内黑点数量均为偶数,那么总方案数就是 (2^{m-n+cnt})
    接下来处理删去一个点的操作。设 (sum) 为黑点数量为奇数的连通块数量,(bel_x) 表示 (x) 所在连通块,(s_i) 表示连通块 (i) 的黑色点奇偶性。显然 (sumgeq 2) 无解。否则分类讨论:

    • 如果这个点没有任何边连接,也就是一个点是一个连通块,那么如果 (sum=col_x),那么答案就是 (2^{m-n+cnt}),否则无解。
    • 如果这个点不是割点,那么如果 (col_x eq s_{bel_x}),显然无解,否则答案就是 (2^{m- ext{deg}_x-n+1+cnt})
    • 如果这个点是割点,那么如果 (col_x eq s_{bel_x}) 无解,否则枚举在搜索树中 (x) 的所有儿子,这些儿子只要有一个不满足子树内黑色节点数量是偶数就无解。如果儿子均满足,显然它父亲那边的子树也是满足的,就不用判断了。如果有解,答案就是 (2^{m- ext{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("
    "); 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("
    ");
    	}
    	return 0;
    }
    
  • 相关阅读:
    java 静态代码块和spring @value等注解注入顺序
    中秋节
    IP切换
    MMF循环队列实现RPC
    Redis 集群方案
    简单Http多线程下载实现
    信息采集
    大四了
    懒懒交流会《前端,架构,框架与库》里面提到的一些问题
    [知识整理] 导数据工具
  • 原文地址:https://www.cnblogs.com/stoorz/p/14454376.html
Copyright © 2011-2022 走看看