【bzoj3772】 精神污染 dfs序+主席树

PoPoQQQ的题解太神了,表示看了好久才看懂,于是自己写了一份题解祸害人类。

    首先有一个结论对于一条路径x-->y,如果它的两个端点x和y都在另一条路径上,则这条路径被另一条路径包含。
    那么问题转化为了对于一条路径,判断两个端点都在这条路径上的路径有多少条。
    那么对于每一个节点x,我们用一个vector来存节点y,当且仅当存在一条路径x-->y。
    我们说主席树相当于前缀和S,那么我们现在要维护的序列A(即要维护前缀和的序列)表示什么呢?
    在主席树中每一个节点的A都是一个序列,序列的下标表示的是每个节点的入栈序和出栈序,那么我们对于每一个节点x,将它的vector中的y节点的入栈序和出栈序在它的A序列中+1和-1,那么我们看一下,假设有路径x-->y,又有路径x-->z,那么后者包含前者时,y的入栈序一定在z的入栈序和出栈序之间,y的出栈序一定在z的出栈序之后,那么我们查询的时候就可以直接查询z的入出栈序之间的权值和,即为有多少条一端是x节点的路径被路径x-->z包含。
    那么在树上建主席树,主席树的前缀和S就可以表示从根节点到这个节点x的路径上的A序列的和,那么查询时我们查询权值(下标)在lca(x,y)与y的入栈序之间的数的个数就是查询起点在根节点到x的路径上的终点在lca(x,y)与y之间的路径的个数。
    那么被路径x-->y包含的路径数目就是,记z为lca(x,y),
    ans=query(in[x])+query(in[y])-query(in[z])-query(in[fa[z]])-1
    其中query操作是在第x棵、第y棵、第z棵、第fa[z]棵中找权值(下标)在1-k之间的数的和,这四颗树可以提前提取出来,最后的-1是把自己给减去。
    最后要处理一下相同路径的问题。

然后并不知道为什么我直接询问前缀和会出错,但是如果区间查询的话就没有问题,这道题因为把4棵树提取出来还要写区间查询太麻烦了,所以直接放在函数里了。(原来可以区间查询呀,貌似跟普通的线段树一样呢)


#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<vector>
#define maxn 100010
#define maxm 4000010

using namespace std;

struct yts
{
	int x,y;
}q[maxn];

int lch[maxm],rch[maxm],cnt[maxm];
int to[2*maxn],next[2*maxn],head[maxn],root[maxn];
int in[maxn],out[maxn],c[5];
int dep[maxn],fa[maxn][20];
int n,m,tot,num,num1;
vector<int> b[maxn];

long long gcd(long long a,long long b)
{
	if (b==0) return a;
	else return gcd(b,a%b);
}

void addedge(int x,int y)
{
	num++;to[num]=y;next[num]=head[x];head[x]=num;
}

void dfs(int x)
{
	in[x]=++num1;
	for (int p=head[x];p;p=next[p])
	  if (to[p]!=fa[x][0])
	  {
	  	fa[to[p]][0]=x;dep[to[p]]=dep[x]+1;
	  	dfs(to[p]);
	  }
	out[x]=++num1;
}

int modify(int pre,int l,int r,int x,int f)
{
	int now=++tot;
	if (l==r)
	{
		cnt[now]=cnt[pre]+f;lch[now]=rch[now]=0;
	}
	else
	{
		int mid=(l+r)/2;
		if (x<=mid)
		{
			rch[now]=rch[pre];lch[now]=modify(lch[pre],l,mid,x,f);
		}
		else
		{
			lch[now]=lch[pre];rch[now]=modify(rch[pre],mid+1,r,x,f);
		}
		cnt[now]=cnt[lch[now]]+cnt[rch[now]];
	}
	return now;
}

int query(int root1,int root2,int root3,int root4,int l,int r,int x,int y)
{
	if (l==x && y==r) return cnt[root1]+cnt[root2]-cnt[root3]-cnt[root4];
	int mid=(l+r)/2;
	if (y<=mid) return query(lch[root1],lch[root2],lch[root3],lch[root4],l,mid,x,y);
	if (mid<x) return query(rch[root1],rch[root2],rch[root3],rch[root4],mid+1,r,x,y);
	return query(lch[root1],lch[root2],lch[root3],lch[root4],l,mid,x,mid)+query(rch[root1],rch[root2],rch[root3],rch[root4],mid+1,r,mid+1,y);
}

void dfs1(int x)
{
	root[x]=root[fa[x][0]];
	for (int i=0;i<b[x].size();i++)
	{
		root[x]=modify(root[x],1,2*n,in[b[x][i]],1);
		root[x]=modify(root[x],1,2*n,out[b[x][i]],-1);
	}
	for (int p=head[x];p;p=next[p])
	  if (to[p]!=fa[x][0])
	    dfs1(to[p]);
}

int go_up(int x,int d)
{
	for (int i=19;i>=0;i--)
	  if (d&(1<<i)) x=fa[x][i];
	return x;
}

int LCA(int x,int y)
{
	if (dep[x]>dep[y]) x=go_up(x,dep[x]-dep[y]);
	else y=go_up(y,dep[y]-dep[x]);
	if (x==y) return x;
	for (int i=19;i>=0;i--)
	  if (fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
	return fa[x][0];	
}

bool cmp(yts a,yts b)
{
	return a.x<b.x || (a.x==b.x && a.y<b.y);
}

int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		addedge(x,y);addedge(y,x);
	}
	for (int i=1;i<=m;i++)
	{
		scanf("%d%d",&q[i].x,&q[i].y);
		b[q[i].x].push_back(q[i].y);
	}
	
	dep[1]=0;fa[1][0]=0;
	dfs(1);
	for (int j=1;j<=19;j++)
	  for (int i=1;i<=n;i++)
	    if (fa[i][j-1]) fa[i][j]=fa[fa[i][j-1]][j-1];
	    else fa[i][j]=0;
	    
	long long ans=0;
	tot=0;root[0]=lch[0]=rch[0]=cnt[0]=0;
	dfs1(1);
	for (int i=1;i<=m;i++)
	{
		int x=q[i].x,y=q[i].y,z=LCA(x,y);
		ans+=query(root[x],root[y],root[z],root[fa[z][0]],1,2*n,in[z],in[x]);
		ans+=query(root[x],root[y],root[z],root[fa[z][0]],1,2*n,in[z],in[y]);
		ans-=query(root[x],root[y],root[z],root[fa[z][0]],1,2*n,in[z],in[z]);
		ans--;
	}
	sort(q+1,q+m+1,cmp);
	int j;
	for (int i=1;i<=m;i=j)
	{
		for (j=i+1;j<=m && q[j].x==q[i].x && q[j].y==q[i].y;j++);
		ans-=(long long)(j-i)*(j-i-1)/2;
	}
	long long b=(long long)m*(m-1)/2,Gcd=gcd(ans,b);
	printf("%lld/%lld\n",ans/Gcd,b/Gcd);
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值