Usmjeri(COCI2017.2)
内存限制:
256 MiB
时间限制:
2000 ms
标准输入输出
题目类型:
传统评测方式:
文本比较
题目描述
给一棵N个节点的树,编号从1到N,再给定m对点(u,v),你要将树上的每条无向边变为有向边,使得给定的点对都满足u能到达v或v能到达u。问有多少种不同的方案,答案对(1e9+7)求余。
输入格式
第一行两个正整数N and M(1 ≤ N, M≤ 3*1e5 ),表示树的结点个数,和点对的个数。
接下来N-1行,每行两个整数,表示树上的边。
接下来M行,每行两个不同的正整数(ai,bi),表示对应的点对,点对互不相同。
输出格式
一行一个数,表示不同的方案数模1e9+7
20%的数据树是一个链,即第i个点连在i+1上。
40%的数据N,M≤ 5*1e3 .
样例
样例输入1
4 1
1 2
2 3
3 4
2 4
样例输出1
4
样例输入2
7 2
1 2
1 3
4 2
2 5
6 5
5 7
1 7
2 6
样例输出2
8
样例输入3
4 3
1 2
1 3
1 4
2 3
2 4
3 4
样例输出3
0
数据范围与提示
A tree is a graph that consists of N nodes and N - 1 edges such that there exists a path from each node to each other node.
题解
本题是一道并查集+lca。先建一棵树,再做出2n个点。通过并查集来维护条件。通过u->lca与v->lca可以构造出一条询问路径,将这条路径加入并查集。最统计并查集个数,因为每个集只有一条路径,可以正或反,又建了一倍,所以只需输出2^(并查集数/2)即可。
源码
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
const int mo=1000000007;
typedef long long LL;
int n,m,dep[300005];
int tot,head[300005];
int f[300005][25];
int _fa[600005],sum;
int A[300005],B[300005],A_B[300005];
struct ming
{
int from,to;
int next;
}edge[600005];
void addEdge(int u,int v)
{
edge[++tot].from=u;edge[tot].to=v;
edge[tot].next=head[u];head[u]=tot;
}//建图
void dfs(int x,int fa)
{
f[x][0]=fa;
for(int i=1;i<20;i++)
f[x][i]=f[f[x][i-1]][i-1];//倍增更新祖先节点
for(int i=head[x];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==fa) continue;
dep[v]=dep[x]+1;//更新深度
dfs(v,x);//更新子节点
}
}
int lca(int u,int v)
{
if(dep[u]>dep[v]) swap(u,v);
for(int i=19;i>=0;i--)
if(dep[f[v][i]]>=dep[u])
v=f[v][i];
if(u==v) return u;
for(int i=19;i>=0;i--)
if(f[u][i]!=f[v][i])
u=f[u][i],v=f[v][i];
return f[u][0];
}//倍增求lca
void makeSet(int x)
{
for(int i=1;i<=x;i++)
_fa[i]=i;
}
int findSet(int x)
{
if(x!=_fa[x])
_fa[x]=findSet(_fa[x]);
return _fa[x];
}
void unionSet(int a,int b)
{
int u=findSet(a),v=findSet(b);
if(u!=v) _fa[u]=v;
}
void conNect(int u,int v)
{
while(dep[f[u][0]]>dep[v])
{
int p=f[u][0];
unionSet(u,p);
unionSet(u+n,p+n);
u=findSet(p);
}
}//更新路径
void conNect2(int u,int v)
{
unionSet(u+n,v);
unionSet(v+n,u);
}//更新路径
LL qkpow(LL a,int s)
{
LL t=1;
while(s)
{
if(s&1)
t=(t*a)%mo;
a=(a*a)%mo;
s>>=1;
}
return t;
}//快速幂
int main()
{
freopen("usmjeri.in","r",stdin);
freopen("usmjeri.out","w",stdout);
scanf("%d %d",&n,&m);
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d %d",&u,&v);
addEdge(u,v);
addEdge(v,u);
}
makeSet(2*n);//多建一倍的节点
dep[1]=0;dfs(1,1);//以1为根,展开搜索
for(int i=1;i<=m;i++)
{
scanf("%d %d",&A[i],&B[i]);
A_B[i]=lca(A[i],B[i]);
conNect(A[i],A_B[i]);
conNect(B[i],A_B[i]);
}//连接a到b的路径
for(int i=1;i<=m;i++)
if(A_B[i]!=A[i]&&A_B[i]!=B[i])
conNect2(A[i],B[i]);//如果不在一条链上,再次连接a到b
for(int i=1;i<=n;i++)
{
int t1=findSet(i);
int t2=findSet(i+n);
if(t1==t2)
{
printf("0");
return 0;
}//有无i与i+n在同一个集
}
for(int i=2;i<=n;i++)
if(_fa[i]==i)
sum++;//统计集数
for(int i=n+2;i<=2*n;i++)
if(_fa[i]==i)
sum++;//统计集数
printf("%lld",qkpow(2,sum/2));//输出结果,为2^(n/2)
return 0;
}