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