bzoj 3772: 精神污染 (主席树+dfs序)

3772: 精神污染

Time Limit: 10 Sec   Memory Limit: 64 MB
Submit: 266   Solved: 72
[ Submit][ Status][ Discuss]

Description

兵库县位于日本列岛的中央位置,北临日本海,南面濑户内海直通太平洋,中央部位是森林和山地,与拥有关西机场的大阪府比邻而居,是关西地区面积最大的县,是集经济和文化于一体的一大地区,是日本西部门户,海陆空交通设施发达。濑户内海沿岸气候温暖,多晴天,有日本少见的贸易良港神户港所在的神户市和曾是豪族城邑“城下町”的姬路市等大城市,还有以疗养地而闻名的六甲山地等。
兵库县官方也大力发展旅游,为了方便,他们在县内的N个旅游景点上建立了n-1条观光道,构成了一棵图论中的树。同时他们推出了M条观光线路,每条线路由两个节点x和y指定,经过的旅游景点就是树上x到y的唯一路径上的点。保证一条路径只出现一次。
你和你的朋友打算前往兵库县旅游,但旅行社还没有告知你们最终选择的观光线路是哪一条(假设是线路A)。这时候你得到了一个消息:在兵库北有一群丧心病狂的香菜蜜,他们已经选定了一条观光线路(假设是线路B),对这条路线上的所有景点都释放了【精神污染】。这个计划还有可能影响其他的线路,比如有四个景点1-2-3-4,而【精神污染】的路径是1-4,那么1-3,2-4,1-2等路径也被视为被完全污染了。
现在你想知道的是,假设随便选择两条不同的路径A和B,存在一条路径使得如果这条路径被污染,另一条路径也被污染的概率。换句话说,一条路径被另一条路径包含的概率。

Input

第一行两个整数N,M
接下来N-1行,每行两个数a,b,表示A和B之间有一条观光道。
接下来M行,每行两个数x,y,表示一条旅游线路。

Output

所求的概率,以最简分数形式输出。

Sample Input

5 3
1 2
2 3
3 4
2 5
3 5
2 5
1 4

Sample Output

1/3
样例解释
可以选择的路径对有(1,2),(1,3),(2,3),只有路径1完全覆盖路径2。

HINT

100%的数据满足:N,M<=100000

Source

[ Submit][ Status][ Discuss]

题解:主席树+dfs序

这道题当时做的时候想了好久,也调了好久。。。呜呜呜

对于每一条链计算能完全覆盖它的有多少条。 
处理出来dfs序了之后,可以发现边大概分为三种情况(我们另x为两个点钟dfs序较小的点):

(1)x和y的lca不是x和y中的某一个,那么所有能覆盖他的路径就是一个端点在x的子树中,另一个在y的子树中。(2)x和y的lca是x或y,能覆盖他的路径一定是一个端点在y的子树中,另一个在x的子树外。

(3)一条路径就是一个点。向第二种情况一样处理,然后路径的lca是该点的数目特殊计算。
用权值线段树套权值线段树即可,内层用动态开点,那么空间和时间都是 O(nlogn) 。外层表示路径端点较小的dfs序,内层表示路径端点较大的dfs序。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#define N 100003
#define LL long long
using namespace std;
int point[N],next[N*2],v[N*2],n,m,tot,sz,root[N],pre;
int l[N],r[N],q[N],deep[N],mi[20],fa[N][20],val[N];
struct data
{
	int l,r,sum;
}tr[N*32];
struct node
{
	int x,y; 
}e[N];
int cmp(node a,node b)
{
	return l[a.x]<l[b.x]||l[a.x]==l[b.x]&&l[a.y]<l[b.y];
}
void add(int x,int y)
{
	tot++; next[tot]=point[x]; point[x]=tot; v[tot]=y;
	tot++; next[tot]=point[y]; point[y]=tot; v[tot]=x;
}
void dfs(int x,int f)
{
	deep[x]=deep[f]+1;
	for (int i=1;i<=17;i++){
		if (deep[x]-mi[i]<0) break;
		fa[x][i]=fa[fa[x][i-1]][i-1];
	}
	l[x]=++sz; q[sz]=x;
	for (int i=point[x];i;i=next[i]){
		if (v[i]==f) continue;
		fa[v[i]][0]=x;
		dfs(v[i],x);
	}
	r[x]=sz; 
}
void insert(int &i,int l,int r,int pos)
{
	tr[++sz]=tr[i]; i=sz;
	tr[i].sum++;
	if (l==r) return;
	int mid=(l+r)/2;
	if (pos<=mid) insert(tr[i].l,l,mid,pos);
	else insert(tr[i].r,mid+1,r,pos);
	//cout<<i<<" "<<l<<" "<<r<<" "<<tr[i].sum<<endl;
}
int query(int l,int r,int i,int j,int ll,int rr)
{
	if (ll<=l&&r<=rr) return tr[j].sum-tr[i].sum;
	int mid=(l+r)/2; int ans=0;
	if (ll<=mid) ans+=query(l,mid,tr[i].l,tr[j].l,ll,rr);
	if (rr>mid) ans+=query(mid+1,r,tr[i].r,tr[j].r,ll,rr);
	return ans;
}
int lca(int x,int y)
{
	if (deep[x]<deep[y]) swap(x,y);
	if (deep[x]!=deep[y]) {
		int k=deep[x]-deep[y]-1;
		for (int i=0;i<=17;i++)
		 if (k>>i&1) x=fa[x][i];
		pre=x;
		x=fa[x][0];
    }
	if (x==y) return x;
	for (int i=17;i>=0;i--)
	 if (fa[x][i]!=fa[y][i])
	  x=fa[x][i],y=fa[y][i];
	pre=x;
	return fa[x][0];
}
LL gcd(LL x,LL y)
{
	LL r;
	while (y){
		r=x%y;
		x=y;
		y=r;
	}
	return x;
}
int main()
{
	freopen("a.in","r",stdin);
	freopen("my.out","w",stdout);
	scanf("%d%d",&n,&m);
	mi[0]=1;
	for (int i=1;i<=17;i++) mi[i]=mi[i-1]*2;
	for (int i=1;i<n;i++) {
		int x,y; scanf("%d%d",&x,&y);
		add(x,y);
	}
	dfs(1,0);
	for (int i=1;i<=m;i++) {
	 scanf("%d%d",&e[i].x,&e[i].y);
	 if (l[e[i].x]>l[e[i].y]) swap(e[i].x,e[i].y);
    }
    //for (int i=1;i<=sz;i++) cout<<q[i]<<" ";
    //cout<<endl;
	sort(e+1,e+m+1,cmp);
	int now=1; int cnt=sz; sz=0;
	for (int i=1;i<=m;i++){
		while (now<=cnt&&l[e[i].x]>now) 
		  root[now+1]=root[now],now++; 
		insert(root[now],1,cnt,l[e[i].y]);
	}
	for (int i=now+1;i<=cnt;i++) root[i]=root[i-1];
	LL ans=0;
	for (int i=1;i<=m;i++){
		if (e[i].x==e[i-1].x&&e[i].y==e[i-1].y) ans--;
		//cout<<e[i].x<<" "<<e[i].y<<" ";
	    int t=lca(e[i].x,e[i].y);   //cout<<t<<endl;
	    if (t!=e[i].x&&t!=e[i].y) {
	     val[t]++;
		 ans+=(LL)query(1,cnt,root[l[e[i].x]-1],root[r[e[i].x]],l[e[i].y],r[e[i].y]);
		 ans--;
	    }
		else if ((t==e[i].x||t==e[i].y)&&e[i].x!=e[i].y) {
		    int ll=l[e[i].x]; int rr=r[e[i].x]; int t1=l[pre]; int t2=r[pre];
			ans+=(LL)query(1,cnt,root[0],root[t1-1],l[e[i].y],r[e[i].y]); 
			ll=l[e[i].y]; rr=r[e[i].y]; //cout<<pre<<endl;
			if (t2+1<=cnt) ans+=(LL)query(1,cnt,root[ll-1],root[rr],t2+1,cnt);
		    ans--;
		}
		else {
			int ll=l[e[i].x]; int rr=r[e[i].x]; 
			ans+=(LL)query(1,cnt,root[0],root[ll],l[e[i].y],r[e[i].y]);
			if (rr+1<=cnt) ans+=(LL)query(1,cnt,root[ll-1],root[rr],rr+1,cnt);
		    ans--;
		}
		//cout<<ans<<endl;
	}
	//for (int i=1;i<=n;i++) cout<<val[i]<<" ";
	//cout<<endl;
	for (int i=1;i<=m;i++) 
	{
	  int t=lca(e[i].x,e[i].y);
	  if (e[i].x==e[i].y) ans+=(LL)val[e[i].x];
    }
	ans=max(ans,0LL);
	LL tot=(LL)(m-1)*m/2; //cout<<ans<<" "<<tot<<endl;
	LL t=gcd(ans,tot); 
	printf("%I64d/%I64d\n",ans/t,tot/t);
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值