【2018/07/26测试T2】【SOJ 1911】植树方案

【题目】

题目描述:

T国打算种一批树。所谓树,就是由 N 个结点与 N-1 条边连接而成的连通无向图。T国的国王对于这些树有下列要求:

1、树没有根,但它的形态是给定的(即这 N-1 条边是给出的);
2、树的每条边上可以放置一朵花(当然也可以不放置);
3、共 Q 条约束,第 i 组约束规定:标号 ui 的结点到标号 vi 的结点的简单路径上,花的数量为奇数或偶数。

现在,国王想事先知道他最多能种多少棵不一样的树(两棵树被视为不一样当且仅当一棵树中某条边的放花情况与另一棵树不相同)。

输入格式:

第 1 行两个整数 N, Q ;
接下来 N-1 行,每行两个整数 u、v,表示 u、v 间存在一条边;
接下来 Q 行,每行三个整数 ui、vi、ki,若 ki 为 0,表示 ui 到 vi 的简单路径上花的个数为偶数,否则为奇数。

输出格式:

一行一个整数,表示能种的不一样的树的种数,对 998244353 取模。

样例数据:

输入
5 2
1 2
1 3
1 5
4 5
1 5 0
2 4 1

输出
4

备注:

【数据范围】
对于 20% 的数据,N,Q ≤ 20;
对于 40% 的数据,N,Q ≤ 100;
对于 70% 的数据,N,Q ≤ 5000;
对于 100% 的数据,N,Q ≤ 100000、0 ≤ ki ≤1。

 

【分析】

这里部分内容是从zwk学长那里找来的,链接[ASDFZ-NOIP2016模拟]植树方案

先把题解发出来吧:

emmm,我也说一下我自己的理解吧。。。

首先,若没有限制,那么我们会有 2^{n-1} 种方案(每条边都可以放花或不放花)

对于一条有限制的边,我画了一个图,S,T 是这个限制的两个端点,边上的数字是我们待会会用到的两条边的编号,中间的虚线代表S,T之间还有很多点

我们考虑一下四种情况:

  • 如果限制是偶数,中间部分有偶数朵花,那我们可以在1,2都放花或都不放,有两种选择
  • 如果限制是偶数,中间部分有奇数朵花,那我们可以在1或者2放花,有两种选择
  • 如果限制是奇数,中间部分有偶数朵花,那我们可以在1或者2放花,有两种选择
  • 如果限制是奇数,中间部分有奇数朵花,那我们可以在1,2都放花或都不放,有两种选择

这样看来的话,加了限制,对中间部分的选取没有影响,我们就直接从连一条S到T的无向边,权值为限制

我们给每个点一个点权,在连通块内任选一点为根,令它的点权为0,然后从它出发遍历连通块内所有的点,权值为0的边两端的权值相同(可以理解成1,2两边都放花或都不放),权值为1的边两端的权值不同(可以理解成在1或者2上放花),每次遍历到一个点,若发现它已经有一个权值并且与现在得到的权值不相等,那么限制之间就存在冲突

如果没有冲突的话答案就是2^(连通块个数 -1)

 

【代码】

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100005
#define M 200005
#define mod 998244353
using namespace std;
int n,m,t;
bool flag;
int a[N],first[N],v[M],w[M],next[M];
void add(int x,int y,int z)
{
	t++;
	next[t]=first[x];
	first[x]=t;
	v[t]=y;
	w[t]=z;
}
void dfs(int x)
{
	int i,j;
	if(a[x]==-1)  a[x]=0;
	for(i=first[x];i;i=next[i])
	{
		j=v[i];
		if(a[j]==-1)
		{
			if(!w[i])
			{
				a[j]=a[x];
				dfs(j);
			}
			else
			{
				a[j]=a[x]^1;
				dfs(j);
			}
		}
		else
		{
			if(!w[i]&&a[j]!=a[x]) 
	                {
	                        flag=false;
	                        return;
	                }
	                if(w[i]&&a[j]==a[x])
	                {
	        	        flag=false;
	                        return;
	                }
		}
	}
}
int main()
{
//	freopen("scheme.in","r",stdin);
//	freopen("scheme.out","w",stdout);
	int x,y,z,i,sum=0,ans=1;
	scanf("%d%d",&n,&m);
	for(i=1;i<n;++i)
	  scanf("%d%d",&x,&y);
	for(i=1;i<=m;++i)
	{
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z);
		add(y,x,z);
	}
	memset(a,-1,sizeof(a));
	for(i=1;i<=n;++i)
	{
		if(a[i]==-1)
		{
			flag=true;
			dfs(i);
			if(!flag)
			{
				printf("0");
				return 0;
			}
			sum++;
		}
	}
	for(i=1;i<sum;++i)
	  ans=(ans<<1)%mod;
	printf("%d",ans);
//	fclose(stdin);
//	fclose(stdout);
	return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值