Description
Input
Output
Sample Input
1 2
2 3
3 4
2 5
3 5
2 5
1 4
Sample Output
样例解释
可以选择的路径对有(1,2),(1,3),(2,3),只有路径1完全覆盖路径2。
HINT
Source
分析:
%PoPoQQQ
看到网上大多的题解都来自PoPoQQQdada的blog,所以也%一发
然而dalao的题解都比较神,不是很明白
首先:如果路径A包含于路径B,那么就有A的两端点在路径B上,这是个充要条件
那么问题转化为了对于一条路径,判断两个端点都在这条路径上的路径有多少条
对于每一个节点x,我们用一个vector来存结点y,当且仅当存在一条路径x–>y
主席树相当于前缀和S
处理出来dfs序了之后,可以发现边大概分为三种情况
(我们令x为两个点中dfs序较小的点)
- x和y的lca不是x和y中的某一个,那么所有能覆盖ta的路径就是一个端点在x的子树中,另一个在y的子树中
- x和y的lca是x或y,能覆盖ta的路径一定是一个端点在y的子树中,另一个在x的子树外
- 一条路径就是一个点,和第二种情况一样处理,其中路径的lca是该点的数目特殊计算
用权值线段树套权值线段树,内层用动态开点
空间和时间都是
O(nlogn)
O
(
n
l
o
g
n
)
外层表示路径端点较小的dfs序,内层表示路径端点较大的dfs序
我能找到的题解最多的也就是上面几句话
所以还是根据代码仔细说一下:
首先建出树来,dfs一遍
得到dfs序以及每个结点代表的子树的出栈入栈位置
读入路径,并且按照dfs序排序
if (in[e[i].x]>in[e[i].y]) swap(e[i].x,e[i].y); //保证x是dfs序中靠前的一个
int cmp(const point &A,const point &B)
{return in[A.x]<in[B.x]||in[A.x]==in[B.x]&&in[A.y]<in[B.y];}
之后就是主席树的构建
我们要按照dfs序insert,但是题目给出的路径不一定包含所有的结点
但是我们要保证:每一个点都有根(即使是光继承上一个)
int now=1;
for (int i=1;i<=m;i++)
{
while (now<=clo&&now<in[e[i].x]) root[now+1]=root[now],now++;
insert(root[now],1,clo,in[e[i].y]); //插入y的入栈位置
}
for (int i=now+1;i<=clo;i++) root[i]=root[i-1];
(一开始在维护root时候,我的写法是:root[i]=root[i-1]
,但是这样写是错的)
在一条路径起点代表的权值线段树中,插入路径终点的入栈位置
这样按照一定顺序插入,在记录答案的时候
对于路径x只询问编号比x小的可行路径数,就可以不重不漏了
现在我们就可以计算答案了
if (e[i].x==e[i-1].x&&e[i].y==e[i-1].y) ans--; //自己对自己的影响不计入答案
情况一: lca(x,y)!=x,lca(x,y)!=y l c a ( x , y ) ! = x , l c a ( x , y ) ! = y
这样的路径一端在x的子树中,一端在y的子树中
主席树是以dfs为基础建造的,里面记录的都是路径的终点
我们可以找到此路径起点x代表的线段树
询问在起点子树x内(
root[in[e[i].x]−1],root[out[e[i].x]]
r
o
o
t
[
i
n
[
e
[
i
]
.
x
]
−
1
]
,
r
o
o
t
[
o
u
t
[
e
[
i
]
.
x
]
]
)
终点在子树y内(
in[e[i].y],out[e[i].y]
i
n
[
e
[
i
]
.
y
]
,
o
u
t
[
e
[
i
]
.
y
]
)
的路径条数
(实际上就是区间
[in[e[i].y],out[e[i].y]]
[
i
n
[
e
[
i
]
.
y
]
,
o
u
t
[
e
[
i
]
.
y
]
]
内的sum)
int p=lca(e[i].x,e[i].y);
if (p!=e[i].x&&p!=e[i].y)
{
val[p]++;
ans+=(ll)ask(root[in[e[i].x]-1],root[out[e[i].x]],1,clo,in[e[i].y],out[e[i].y]);
ans--; //自己对自己的影响不计入答案
}
情况二: lca(x,y)=x||y l c a ( x , y ) = x | | y
这样的路径一端在y的子树中,一端在x的子树外
其中,X是lca的儿子
在dfs序中,非x子树分成了两部分:
[0,in[X]−1],[out[X]+1,n]
[
0
,
i
n
[
X
]
−
1
]
,
[
o
u
t
[
X
]
+
1
,
n
]
但是我们不能无脑的询问:
ask(root[0],root[in[X]-1],in[e[i].y],out[e[i].y])
ask(root[out[X]+1],root[n],in[e[i].y],out[e[i].y])
为什么?
因为主席树的建立是按照dfs序的,如果询问
(root[out[X]+1],root[n])
(
r
o
o
t
[
o
u
t
[
X
]
+
1
]
,
r
o
o
t
[
n
]
)
就不可能“对于路径x只询问编号比x小的可行路径数,不重不漏”
因此我们要询问:
ask(root[0],root[in[X]-1],in[e[i].y],out[e[i].y])
ask(root[in[e[i].y]-1],root[out[e[i].y]],out[X],n)
if ((p==e[i].x||p==e[i].y)&&(e[i].x!=e[i].y))
{
int L=in[e[i].x],R=out[e[i].x];
int t1=in[X],t2=out[X];
ans+=(ll)ask(root[0],root[t1-1],1,clo,in[e[i].y],out[e[i].y]);
L=in[e[i].y],R=out[e[i].y];
if (t2+1<=clo)
ans+=(ll)ask(root[L-1],root[R],1,clo,t2+1,clo);
ans--;
}
情况二:单点路径
像情况二一样处理
int L=in[e[i].x],R=out[e[i].x];
ans+=(ll)ask(root[0],root[L],1,clo,in[e[i].y],out[e[i].y]);
if (R+1<=clo) ans+=(ll)ask(root[L-1],root[R],1,clo,R+1,clo);
ans--;
最后
对于单点路径,我们之前只计算了两端点不在一个子树内的路径数
这里还要计算折线路径的个数
val
v
a
l
:
lca
l
c
a
是
e[i].x
e
[
i
]
.
x
的路径条数
for (int i=1;i<=m;i++) //处理单点的路径
{
int p=lca(e[i].x,e[i].y);
if (e[i].x==e[i].y) ans+=(ll)val[e[i].x];
}
tip
一定要注意写主席树的时候要保证每一个点都有根(即使是光继承上一个),否则的话很可能减出负数来
注意:自身产生的影响不计入答案(每一种情况都要考虑这点)
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=100010;
struct point{
int x,y;
}e[N];
struct node{
int y,nxt;
}way[N<<1];
struct Tree{
int l,r,sum;
}t[N*20];
int n,m,st[N],tot=0,lg,top=0,X,root[N],val[N];
int clo=0,dfn[N],deep[N],pre[N][20],in[N],out[N];
void add(int x,int y)
{
tot++;way[tot].y=y;way[tot].nxt=st[x];st[x]=tot;
tot++;way[tot].y=x;way[tot].nxt=st[y];st[y]=tot;
}
int cmp(const point &A,const point &B) {return in[A.x]<in[B.x]||in[A.x]==in[B.x]&&in[A.y]<in[B.y];}
ll gcd(ll a,ll b)
{
ll r=a%b;
while (r)
{
a=b; b=r;
r=a%b;
}
return b;
}
void dfs(int now,int fa,int dep)
{
deep[now]=dep;
pre[now][0]=fa;
for (int i=1;i<=lg;i++)
{
if (deep[now]-(1<<i)<0) break;
pre[now][i]=pre[pre[now][i-1]][i-1];
}
dfn[++clo]=now; in[now]=clo;
for (int i=st[now];i;i=way[i].nxt)
if (way[i].y!=fa)
dfs(way[i].y,now,dep+1);
out[now]=clo;
}
void insert(int &now,int l,int r,int x)
{
top++;
t[top]=t[now];
now=top;
t[now].sum++;
if (l==r) return;
int mid=(l+r)>>1;
if (x<=mid) insert(t[now].l,l,mid,x);
else insert(t[now].r,mid+1,r,x);
}
int ask(int x,int y,int l,int r,int L,int R)
{
if (l>=L&&r<=R) return t[y].sum-t[x].sum;
int mid=(l+r)>>1;
int ans=0;
if (L<=mid) ans+=ask(t[x].l,t[y].l,l,mid,L,R);
if (R>mid) ans+=ask(t[x].r,t[y].r,mid+1,r,L,R);
return ans;
}
int lca(int x,int y)
{
if (deep[x]<deep[y]) swap(x,y);
int d=deep[x]-deep[y]-1;
if (d)
for (int i=0;i<=lg&&d;i++,d>>=1)
if (d&1)
x=pre[x][i];
X=x;
x=pre[x][0];
if (x==y) return x;
for (int i=lg;i>=0;i--)
if (pre[x][i]!=pre[y][i])
x=pre[x][i],y=pre[y][i];
X=x;
return pre[x][0];
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
}
lg=log(n)/log(2);
dfs(1,0,1);
for (int i=1;i<=m;i++)
{
scanf("%d%d",&e[i].x,&e[i].y);
if (in[e[i].x]>in[e[i].y]) swap(e[i].x,e[i].y); //保证x是dfs序中靠前的一个
}
sort(e+1,e+1+m,cmp); //按照dfs序插入
int now=1;
for (int i=1;i<=m;i++)
{
while (now<=clo&&now<in[e[i].x]) root[now+1]=root[now],now++;
insert(root[now],1,clo,in[e[i].y]); //插入y的入栈位置
}
for (int i=now+1;i<=clo;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--; //自己对自己的影响不计入答案
int p=lca(e[i].x,e[i].y);
if (p!=e[i].x&&p!=e[i].y)
{
val[p]++;
ans+=(ll)ask(root[in[e[i].x]-1],root[out[e[i].x]],1,clo,in[e[i].y],out[e[i].y]);
ans--; //自己对自己的影响不计入答案
}
else if ((p==e[i].x||p==e[i].y)&&(e[i].x!=e[i].y))
{
int L=in[e[i].x],R=out[e[i].x];
int t1=in[X],t2=out[X];
ans+=(ll)ask(root[0],root[t1-1],1,clo,in[e[i].y],out[e[i].y]);
L=in[e[i].y],R=out[e[i].y];
if (t2+1<=clo)
ans+=(ll)ask(root[L-1],root[R],1,clo,t2+1,clo);
ans--;
}
else
{
int L=in[e[i].x],R=out[e[i].x];
ans+=(ll)ask(root[0],root[L],1,clo,in[e[i].y],out[e[i].y]);
if (R+1<=clo) ans+=(ll)ask(root[L-1],root[R],1,clo,R+1,clo);
ans--;
}
}
for (int i=1;i<=m;i++) //处理单点的路径
{
int p=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 tt=(ll)m*(m-1)/2;
ll r=gcd(ans,tt);
printf("%lld/%lld",ans/r,tt/r);
return 0;
}