2018 CodeM复赛:C. 边的染色

链接:https://www.nowcoder.com/acm/contest/152/C
来源:牛客网

题目描述

    小团有一张n个点,m条边的无向图G,有些边上已经被标记了0或1,表示它的边权。
    现在你需要给剩下的边标记边权为0或1,求有几种标记的方式满足:
    对于G中任意一个环,里面所有边的边权的异或值为0。
    环的定义如下:
    对于任意k(k≥2)个点{a 1,a 2,...,a k},若对于所有的i<k满足a i与a i+1之间有边,且a 1=a k成立,则这k个点构成一个环。

输入描述:

第一行两个整数n, m。

接下来m行,每行3个整数u, v, w,表示有一条边(u,v),若w=-1则这条边上没有标记,否则w=0或1表示这条边上标记了w。

数据保证没有重边和自环。

1≤n≤100,000
0≤m≤min(n*(n-1)/2, 100000)

输出描述:

输出方案数对998,244,353取模的值。


先考虑一个简单的题:

题目:一个n个点m条边的无向联通图,要求你给每条边指定一个值,只能是0或1,使得最后所有的环边权异或和为0,求出任意一种方案即可

思路:先给每个点指定一个值0或1,随意指定,然后计算每条边的权值就为两个端点权值的异或,这样就一定满足要求,因为对于任意一个环,每个点权值刚好计算2次


再考虑一个稍微难点的题:

题目:一个n个点m条边的无向图,要求你给每条边指定一个值,只能是0或1,使得最后所有的环边权异或和为0,问有多少种不同的方案:

思路:这个图未必连通,先假设它是连通的,考虑上一道题的做法:给每个点随意指定0和1,然后通过点权计算边权最后都一定合法,而每个点都有赋0赋1两种不同方案,所以总共用2^n种不同方案,不过当中会不会有边权完全一样但点值不同情况呢?有,不过当前仅当所有点的值全部取反时才会一样,所以最后还要除以2,这道题的答案就是2^(n-1)

那么不连通这么办?只要对于每个联通图求出方案数然后乘起来即可


然后就是这道题

题目:这道题相对上道题只多了一个限制:有些边的权值已经给定了,你不能修改,求方案数

思路:还是考虑对于每个联通图求出方案数然后乘起来即可,其实上道题会做这道题就很简单了,先用二分图染色的方法判断是否本身就不合法,如果本身就不合法直接输出0,然后只要把那些有限制的边构成的所有联通块全部揪出来,这些联通块很显然1个端点的值确定了,那么整个联通块所有端点的值就全部确定了,直接答案除掉2^(p[i]-1)即可,其中p[i]是第i个联通块点的个数


#include<stdio.h>
#include<vector>
#include<string.h>
using namespace std;
#define LL long long
#define mod 998244353
typedef struct
{
	int x, val;
}Res;
Res now;
vector<Res> G[100005];
int cnt, sum, ok, vis[100005], scc[100005];
LL all[100005], er[100005] = {1};
LL Pow(LL x, LL y)
{
	LL ans = 1;
	while(y)
	{
		if(y%2)
			ans = ans*x%mod;
		x = x*x%mod;
		y /= 2;
	}
	return ans;
}
void Sech(int u)
{
	int i, v, val;
	sum++;
	for(i=0;i<G[u].size();i++)
	{
		if(G[u][i].val==-1)
			continue;
		v = G[u][i].x;
		val = vis[u]^G[u][i].val;
		if(vis[v]==-1)
		{
			vis[v] = val;
			Sech(v);
		}
		else if(vis[v]!=val)
			ok = 1;
	}
}
void Sech2(int u, int k)
{
	int i, v;
	sum++;
	scc[u] = k;
	for(i=0;i<G[u].size();i++)
	{
		v = G[u][i].x;
		if(scc[v]==0)
			Sech2(v, k);
	}
}
int main(void)
{
	LL ans;
	int x, y, val, n, m, i;
	scanf("%d%d", &n, &m);
	for(i=1;i<=m;i++)
	{
		scanf("%d%d%d", &x, &y, &val);
		now.x = y, now.val = val;
		G[x].push_back(now);
		now.x = x;
		G[y].push_back(now);
	}
	for(i=1;i<=n;i++)
		er[i] = er[i-1]*2%mod;
	memset(vis, -1, sizeof(vis));
	for(i=1;i<=n;i++)
	{
		if(vis[i]==-1)
		{
			vis[i] = 0;
			Sech(i);
		}
	}
	if(ok)
		printf("0\n");
	else
	{
		ans = 1;
		for(i=1;i<=n;i++)
		{
			if(scc[i]==0)
			{
				sum = 0;
				Sech2(i, ++cnt);
				all[cnt] = er[sum-1];
			}
		}
		memset(vis, -1, sizeof(vis));
		for(i=1;i<=n;i++)
		{
			if(vis[i]==-1)
			{
				sum = 0;
				vis[i] = 0;
				Sech(i);
				all[scc[i]] = all[scc[i]]*Pow(er[sum-1], mod-2)%mod;
			}
		}
		for(i=1;i<=cnt;i++)
			ans = ans*all[i]%mod;
		printf("%lld\n", ans);
	}
	return 0;
}
/*
3 3
1 2 1
2 3 1
3 1 1
*/


  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值