Usmjeri

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;
}

谢谢观赏!!!

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值