题目
题目链接:https://codeforces.com/problemset/problem/235/D
求基环树随机点分治总遍历次数期望。
基环树随机点分治步骤:
- 遍历当前分治区域所有点一次。
- 在当前分治区域随机选择一个点 (x)。
- 将 (x) 删掉,产生的所有连通块递归处理。
(nleq 3000)。
思路
考虑枚举两个点 (x,y),(y) 能给 (x) 的贡献,其实就是当 (x) 作为分治中心的时候,(y) 和 (x) 连通的期望。
如果在树上,设 (x,y) 两点之间有 (d) 个点,很显然 (x) 和 (y) 连通当前仅当 (x) 是 (x) 到 (y) 的链上第一个被删除的点,它的概率也就是 (frac{1}{d})。
这道题给出的是一棵基环树,如果两个点之间的路径只有 (1) 条,那么贡献就和树上一样。如果两点之间的路径有两条,容斥一下,设两条路径各自的点数分别为 (d1,d2),两条路径的并集的点数为 (d3),那么这一组点的贡献就是 (frac{1}{d1}+frac{1}{d2}-frac{1}{d3})。
时间复杂度 (O(n^2))。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=3010;
int n,m,tot,head[N],id[N],siz[N],rk[N],top[N],deg[N],dep[N],a[N];
double ans;
struct edge
{
int next,to;
}e[N*2];
void add(int from,int to)
{
e[++tot]=(edge){head[from],to};
head[from]=tot;
}
void topsort()
{
queue<int> q;
for (int i=1;i<=n;i++)
if (deg[i]==1) q.push(i);
while (q.size())
{
int u=q.front(); q.pop();
for (int i=head[u];~i;i=e[i].next)
{
int v=e[i].to;
deg[v]--;
if (deg[v]==1) q.push(v);
}
}
}
void dfs1(int x,int fa,int tp)
{
top[x]=tp; dep[x]=dep[fa]+1;
id[x]=++tot; rk[tot]=x; siz[x]=1;
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (v!=fa && deg[v]<=1)
{
dfs1(v,x,tp);
for (int j=id[x];j<id[x]+siz[x];j++)
for (int k=id[v];k<id[v]+siz[v];k++)
ans+=2.0/(dep[rk[j]]+dep[rk[k]]-2*dep[x]+1);
siz[x]+=siz[v];
}
}
}
void dfs2(int x,int fa,int tp)
{
a[x]=++m;
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (deg[v]>=2 && v!=fa && v!=tp)
return (void)(dfs2(v,x,tp));
}
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d",&n);
for (int i=1,x,y;i<=n;i++)
{
scanf("%d%d",&x,&y);
x++; y++;
add(x,y); add(y,x);
deg[x]++; deg[y]++;
}
topsort();
tot=0;
for (int i=1;i<=n;i++)
if (deg[i]>=2) dfs1(i,0,i);
for (int i=1;i<=n;i++)
if (deg[i]>=2) { dfs2(i,0,i); break; }
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
if (top[i]!=top[j])
{
int d1=abs(a[top[i]]-a[top[j]])-1,d2=m-d1-2;
ans+=1.0/(dep[i]+dep[j]+d1)+1.0/(dep[i]+dep[j]+d2);
ans-=1.0/(dep[i]+dep[j]+m-2);
}
printf("%.12lf",ans+n);
return 0;
}