植树方案
(scheme.cpp/c/pas)
题目描述
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 取模。
样例数据
scheme.in
5 2
1 2
1 3
1 5
4 5
1 5 0
2 4 1
scheme.out
4
【数据范围】
对于 20%的数据, N, Q ≤ 20;
对于 40%的数据, N, Q ≤ 100;
对于 70%的数据, N, Q ≤ 5000;
对于 100%的数据, N, Q ≤ 100000、 0 ≤
ki
≤ 1。
分析:首先得知道一个性质:奇数^奇数=偶数,偶数^偶数=偶数,偶数^奇数=奇数,这与奇偶的加减是完全相同的。
本来,我们做出的图是这样的(发现自己画的不是树,但是不要在意这些细节啦,中间的点点代表还有很多中间点):
然后,根据限制条件我们把图简化成这样(同一种颜色代表他们需要是一致的【在同一联通块】,要么都种花要么都不种,反之表示需要相反【在不同联通块】,可能会有点懵逼,下面我再解释一下):
从原图我们知道,我们简化前其实一条路径的两头的两条边是我们的起点终点可以控制的,比如说如果中间省略部分有奇数朵花且这条路径上要求有偶数朵花,那么我们在这两条边上可以选择在其中一条上种花,有两种可能,又比如说如果中间省略部分有偶数朵花且这条路径上要求有偶数朵花,那么我们在这两条边上可以选择在其中两条上都种花或者都不种,也有两种可能。而对于那些被简化掉的点,没有任何边与之相连,所以自己是一个联通块,有种和不种两种。故有多少个联通块,就乘上2的多少次方(之前的版本理解有误,2017.6.16更新)。
代码
可结合我简陋的图理解
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
#include<queue>
#include<set>
using namespace std;
const int N=100010;
const int mod=998244353;
int n,m,tot,ans,cnt,bz;
int c[N],first[N],next[N<<1],to[N<<1],w[N<<1];
int getint()
{
int f=1,sum=0;
char ch;
for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
if(ch=='-')
{
f=-1;
ch=getchar();
}
for(;ch>='0'&&ch<='9';ch=getchar())
sum=(sum<<3)+(sum<<1)+ch-48;
return f*sum;
}
void addedge(int x,int y,int z)//将有用的部分建边
{
next[++tot]=first[x];
first[x]=tot;
to[tot]=y;
w[tot]=z;
}
void dfs(int u)
{
if(c[u]==-1)
c[u]=0;
for(int e=first[u];e;e=next[e])//走这个点需要满足条件所建的边
{
int v=to[e];
if(c[v]==-1)
if(w[e]==0)//要求是偶数
{
c[v]=c[u];//和起点加入一个联通块(即要么都放花要么都不放)
dfs(v);
}
else//要求是奇数
{
c[v]=c[u]^1;//不在一个联通块(即一个放一个不放)
dfs(v);
}
else//如果终点已经有要求了就做判断(两种矛盾)
if(w[e]==0&&c[v]!=c[u])
{
bz=1;
return;
}
if(w[e]==1&&c[v]==c[u])
{
bz=1;
return;
}
}
}
int main()
{
freopen("scheme.in","r",stdin);
freopen("scheme.out","w",stdout);
memset(c,-1,sizeof(c));
int x,y,z;
n=getint();m=getint();
for(int i=1;i<n;++i)
{
x=getint();y=getint();//前面建树的部分没有用(因为知道有n-1条边,本题只需知道有多少个联通块)
}
while(m--)//把有限制的部分两端点建边
{
x=getint();y=getint();z=getint();
addedge(x,y,z);
addedge(y,x,z);
}
for(int i=1;i<=n;++i)//构造联通块
{
if(c[i]==-1)
{
bz=0;
dfs(i);
if(bz)//bz为1说明无法构造出满足要求的联通块群(就是出现了要求同一点在两个不同联通块中)
{
cout<<0<<endl;
return 0;
}
else//联通块数量+1
cnt++;
}
}
ans=1;
for(int i=1;i<cnt;++i)//每个联通块都有两种可能
ans=(ans<<1)%mod;
cout<<ans<<endl;
return 0;
}
本题结。