题外话: 这题开始并没有什么思路,第二天参考并学习了雨神的思路和写法。感谢雨神大大和清楚姐^-^
题意: 给出n个点和m条边的无向图,有一些边被‘0’或者‘1’染色了. 问你:给没有染色的边染色,使得一个环的异或值为0.求这样满足条件的环有多少种染色方案。
首先,异或^ 。如果二进制同一位置上数相同,返回0,否则返回1. 如果2个相同的数异或一下,结果是0;不相同的数异或一下,结果是1.
如果说从边不好下手的话,我们可以先从点开始分析,首先如果n个点进行0/1染色的话,有2^n次种方案。
这2^n次种方案都是异或和为0? 答案是YES
因为把2个点权异或成边权,每个点会被异或2次,2次相当于抵消了,所以结果就是0。看下图:
那么把点的情况转化成边的情况的话,方案数会减少多少?
举一个例子:
总的方案数其实就是连通块中有多少个节点,然后对应的方案数就是2^(k-1),但是现在题目有一个条件,就是说,他可能会给出一些边权,我们上面讨论的是无任何边权的情况。 有边权的话,在边权连出来的连通块中,只有一个点能选择0或者1,只要那个点被确定了,剩下的点都能唯一确定,因为他有边权的限制。我们把这个连通块的大小为k的话,答案要/2^(k-1)
所以我们只要先求出所有的连通块的大小之和,然后减掉有边权的连通块的大小。
最后的结果就是2的个数,乘起来即可
最后如果出现矛盾的情况,我们直接返回0.
矛盾样例如下:
4 4
1 2 1
2 3 1
3 4 1
4 1 0
结果 0
AC代码:
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include <map>
#include <queue>
#include <deque>
#include <set>
#include <stack>
#include <cctype>
#include <cmath>
#include <cassert>
using namespace std;
typedef long long ll;
const ll mod=1000000007;
ll powmod(ll a,ll b) {ll res=1;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
ll gcd(ll a,ll b) { return b?gcd(b,a%b):a;}
template<class T>inline void read(T &res)
{
char c;T flag=1;
while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;res=c-'0';
while((c=getchar())>='0'&&c<='9')res=res*10+c-'0';res*=flag;
}
const int N=100000+100;
const int MOD=998244353;
int head[N],tot;//链式前向星的变量
int n,m;//n个顶点,m条边
int x,y,val;//读入m条边的信息
int color[N];//存每个点染的颜色
int vis[N];//这个点是否被标记
int sum;
struct Edge
{
int e;
int to;
int val;
}edge[N<<1];
void add_edge(int u,int v,int val)
{//加边操作
tot++;
edge[tot].e=v;
edge[tot].to=head[u];
edge[tot].val=val;
head[u]=tot;
}
int dfs(int u,int col)//u表示当前节点,col表示涂的颜色
{
color[u]=col;
for(int i=head[u];i!=0;i=edge[i].to)
{
int v=edge[i].e;//和当前u节点相连的下一个节点 v
int w=edge[i].val;//和当前u节点相连的下一个节点v的边权
if(w==-1)continue;//我们只看边权有的
if(color[v]!=-1)
{//如果节点v已经被染过色了,那么我们需要和u节点异或一下,看看结果和他两直接的边权吻合吗
if(color[u]^color[v]!=w)
{//如果不等于,就发发生矛盾了
return 0;
}
}
if(color[v]==-1)
{//如果节点v没有被颜色,那么就递归染色下去
int t=dfs(v,col^w);//下一个点的颜色是边权异或前一个颜色,这样能保证2个节点异或一下等于他的边权
if(t==0)return 0;
}
}
return 1;
}
void dfs1(int u)
{
sum++;
vis[u]=1;
for(int i=head[u];i!=0;i=edge[i].to)
{
int v=edge[i].e;
if(!vis[v])dfs1(v);
}
}
void dfs2(int u)
{
sum++;
vis[u]=1;
for(int i=head[u];i!=0;i=edge[i].to)
{
int v=edge[i].e;
int w=edge[i].val;
if(w==-1)continue;
if(!vis[v])dfs2(v);
}
}
int main()
{
scanf("%d%d",&n,&m);
//n个顶点,m条边
for(int i=0;i<m;i++)
{
scanf("%d%d%d",&x,&y,&val);
//val=-1表示没有标记,0或者1表示标记了
//无向图正反存2次
add_edge(x,y,val);
add_edge(y,x,val);
}
memset(color,-1,sizeof(color));//-1表示这个点没有被标记颜色
//首先我们要看已经被标记的边是不是存在前后矛盾的情况,如果存在直接输出0
for(int i=1;i<=n;i++)
{
if(color[i]==-1)//如果当前的颜色没有被标记过
{
int f=dfs(i,1);//把这个点先涂上颜色为1
if(f==0)
{//出现矛盾
printf("0\n");
return 0;
}
}
}
//求连通块点的个数
memset(vis,0,sizeof(vis));
int ans=0;
for(int i=1;i<=n;i++)
{
sum=0;
if(!vis[i])
{
dfs1(i);
ans+=(sum-1);
}
}
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++)
{
sum=0;
if(!vis[i])
{
dfs2(i);
ans-=(sum-1);
}
}
ll res=1;
for(int i=0;i<ans;i++)
{
res=(res*2ll)%MOD;
}
printf("%lld\n",res);
return 0;
}
京公网安备 11010502036488号