传送门
对于每一条链计算能完全覆盖它的有多少条。
处理出来dfs序了之后,可以发现边大概分为三种情况:x和y的lca不是x和y中的某一个,x和y的lca是x或y,还有就是一条路径就是一个点。对于第一种情况,能覆盖它的路径一定是一个端点在x的子树里,一个端点在y的子树里。对于第二种情况,能覆盖它的路径一定是一个端点在y的子树里,另外一个端点在x的子树外。对于第三种情况,除了计算和第二种情况相似的之外,还要计算有多少条路径的lca是这个点,也就是覆盖了这个点。
用权值线段树套权值线段树即可,内层用动态开点,那么空间和时间都是 O(nlogn) 。外层表示路径端点较小的dfs序,内层表示路径端点较大的dfs序。
刚开始竟然手残把lca写错了,gg。
#include
#include
#include
#include
using namespace std;
#define LL long long
#define N 100005
#define sz 17
int n,m,x,y,dfs_clock,last,size,rest;
struct hp{int x,y,r,last;}e[N];
int tot,point[N],nxt[N*2],v[N*2];
int h[N],in[N],out[N],root[N],f[N][sz+3],cnt[N];
LL sum[N*20];int ls[N*20],rs[N*20];
LL up,down;
int cmp(hp a,hp b)
{
return in[a.x]<in[b.x]||(in[a.x]==in[b.x]&&in[a.y]<in[b.y]);
}
void add(int x,int y)
{
++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;
}
void dfs(int x,int fa)
{
in[x]=++dfs_clock;h[x]=h[fa]+1;
for (int i=1;iif (h[x]-(1<1) break;
f[x][i]=f[f[x][i-1]][i-1];
}
for (int i=point[x];i;i=nxt[i])
if (v[i]!=fa)
{
f[v[i]][0]=x;
dfs(v[i],x);
}
out[x]=dfs_clock;
}
int lca(int x,int y)
{
if (x==y) {last=x;return x;}
if (h[x]for (int i=sz-1;i>=0;--i)
while (h[f[x][i]]>h[y])
x=f[x][i];
last=x;
if (h[x]>h[y]) x=f[x][0];
if (x==y) return x;
for (int i=sz;i>=0;--i)
if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
void update(int &now,int l,int r,int x)
{
int mid=(l+r)>>1;
sum[++size]=sum[now]+1,ls[size]=ls[now],rs[size]=rs[now];now=size;
if (l==r) return;
if (x<=mid) update(ls[now],l,mid,x);
else update(rs[now],mid+1,r,x);
}
LL query(int now,int l,int r,int lrange,int rrange)
{
int mid=(l+r)>>1;
LL ans=0;
if (lrange<=l&&r<=rrange) return sum[now];
if (lrange<=mid) ans+=query(ls[now],l,mid,lrange,rrange);
if (mid+1<=rrange) ans+=query(rs[now],mid+1,r,lrange,rrange);
return ans;
}
LL gcd(LL a,LL b)
{
if (!b) return a;
else return gcd(b,a%b);
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i"%d%d",&x,&y);
add(x,y);add(y,x);
}
dfs(1,0);
for (int i=1;i<=m;++i)
{
scanf("%d%d",&x,&y);
if (in[x]>in[y]) swap(x,y);
e[i].x=x,e[i].y=y;e[i].r=lca(x,y);cnt[e[i].r]++;
e[i].last=last;
}
sort(e+1,e+m+1,cmp);
for (int i=1;i<=m;++i)
{
if (i!=1&&in[e[i-1].x]!=in[e[i].x]-1)
for (int j=in[e[i-1].x]+1;j<in[e[i].x];++j) root[j]=root[j-1];
root[in[e[i].x]]=root[in[e[i-1].x]];
update(root[in[e[i].x]],1,n,in[e[i].y]);
}
for (int i=in[e[m].x]+1;i<=n;++i) root[i]=root[i-1];
for (int i=1;i<=m;++i)
{
int r=e[i].r;last=e[i].last;
if (r==e[i].x)
{
if (e[i].x!=e[i].y)
{
int a=1,b=in[last]-1,c=in[e[i].y],d=out[e[i].y];
if (a<=b)
up+=query(root[b],1,n,c,d)-query(root[a-1],1,n,c,d);
a=in[e[i].y],b=out[e[i].y],c=out[last]+1;d=n;
if (c<=d)
up+=query(root[b],1,n,c,d)-query(root[a-1],1,n,c,d);
}
else
{
int a=1,b=in[last]-1,c=in[last],d=out[last];
if (a<=b)
up+=query(root[b],1,n,c,d)-query(root[a-1],1,n,c,d);
a=in[last],b=out[last],c=out[last]+1,d=n;
if (c<=d)
up+=query(root[b],1,n,c,d)-query(root[a-1],1,n,c,d);
up+=(LL)cnt[last];
}
}
else
{
int a=in[e[i].x],b=out[e[i].x],c=in[e[i].y],d=out[e[i].y];
up+=query(root[b],1,n,c,d)-query(root[a-1],1,n,c,d);
}
}
up-=m;down=(LL)m*((LL)m-1)/2;
LL t=gcd(up,down);up/=t,down/=t;
printf("%lld/%lld\n",up,down);
return 0;
}
一定要注意写主席树的时候要保证每一个点都有根(即使是光继承上一个),否则的话很可能减出负数来。