BZOJ3451 Normal 和 CF235D Graph Game
Normal
定义一次点分治的复杂度是所有分治中心分治时的子树大小之和。
给定一棵树,问所有点等概率被选做重心,点分治的期望复杂度。
\(n \leq 30000\)。
题解
https://www.cnblogs.com/SGCollin/p/10597925.html
根据期望的线性性,答案等价于每个点在点分树上的深度期望之和。
思路是从点对的角度考虑某一个点是否会产生贡献。
也就是 \(x\) 在点分树上在 \(1\dots n\) 的子树中的概率和。
考虑点分树上 \(y\) 是 \(x\) 的祖先的条件,要求 \(x\) 和 \(y\) 构成的这条链上第一个在点分治过程中被删除的点是 \(y\) ,由于链上被选中的概率相等,因此这个概率为 \(\frac{1}{dis(x,y) + 1}\)。
所以答案为
因此需要点分治求长度为 \(i\) 的路径条数 \(cnt_i\) ,注意到合并的时候是卷积的形式。
容斥做法
不考虑重复路径,把子树DFS一遍,直接自己和自己进行卷积,再去掉子树内重复计数的路径即可。
每一层最差以自己的size作为长度进行卷积,因此复杂度为 \(O(n\log^2 n)\)。
CO int N=1<<16;
int omg[2][N],rev[N];
void NTT(int a[],int lim,int dir){
int len=log2(lim);
for(int i=0;i<lim;++i) rev[i]=rev[i>>1]>>1|(i&1)<<(len-1);
for(int i=0;i<lim;++i)if(i<rev[i]) swap(a[i],a[rev[i]]);
for(int i=1;i<lim;i<<=1)
for(int j=0;j<lim;j+=i<<1)
for(int k=0;k<i;++k){
int t=mul(omg[dir][N/(i<<1)*k],a[j+i+k]);
a[j+i+k]=add(a[j+k],mod-t),a[j+k]=add(a[j+k],t);
}
if(dir==1){
int ilim=fpow(lim,mod-2);
for(int i=0;i<lim;++i) a[i]=mul(a[i],ilim);
}
}
vector<int> to[N];
bool vis[N];
int siz[N],all;
pair<int,int> root;
void find_root(int u,int fa){
siz[u]=1;
int big=0;
for(int v:to[u])if(v!=fa and !vis[v]){
find_root(v,u);
siz[u]+=siz[v],big=max(big,siz[v]);
}
big=max(big,all-siz[u]);
root=min(root,make_pair(big,u));
}
int buc[N],len,cnt[N];
void dfs(int u,int fa,int dep){
++buc[dep],len=max(len,dep);
for(int v:to[u])if(v!=fa and !vis[v]) dfs(v,u,dep+1);
}
void calc(int u,int o){
len=0,dfs(u,0,0);
int lim=1<<int(ceil(log2(2*len+1)));
NTT(buc,lim,0);
for(int i=0;i<lim;++i) buc[i]=mul(buc[i],buc[i]);
NTT(buc,lim,1);
if(o>0){
for(int i=0;i<=2*len;++i) cnt[i]+=buc[i];
}
else{
for(int i=0;i<=2*len;++i) cnt[i+2]-=buc[i];
}
memset(buc,0,lim*sizeof(int));
}
void divide(int u){
vis[u]=1;
calc(u,1);
int oall=all;
for(int v:to[u])if(!vis[v]){
calc(v,-1);
all=siz[v]<siz[u]?siz[v]:oall-siz[u],root=make_pair(all,0);
find_root(v,0),divide(root.second);
}
}
int main(){
omg[0][0]=1,omg[0][1]=fpow(3,(mod-1)/N);
omg[1][0]=1,omg[1][1]=fpow(332748118,(mod-1)/N);
for(int i=2;i<N;++i){
omg[0][i]=mul(omg[0][i-1],omg[0][1]);
omg[1][i]=mul(omg[1][i-1],omg[1][1]);
}
int n=read<int>();
for(int i=1;i<n;++i){
int u=read<int>()+1,v=read<int>()+1;
to[u].push_back(v),to[v].push_back(u);
}
all=n,root=make_pair(all,0);
find_root(1,0),divide(root.second);
LD ans=0;
for(int i=0;i<=n;++i) ans+=(LD)cnt[i]/(i+1);
printf("%.4Lf\n",ans);
return 0;
}
看了别人的代码,才发现之前自己的预处理原根是多么智障。
Graph Game
给你⼀个 \(n\) 个点,\(n\) 条边的连通图。每次随机选⼀个点删除,然后递归所有连通分量,代价是每次连通块⼤⼩。
问代价期望。\(3 ≤ n ≤ 3000\)。
题解
https://www.cnblogs.com/yzxverygood/p/10406215.html
也就是需要对两点之间的路径经过环的概率容斥一下。
CO int N=3e3+10;
vector<int> to[N];
int deg[N],vis[N],cir[N],len;
int top[N],fa[N][12],dep[N];
void dfs(int u){
cir[++len]=u,vis[u]=len;
for(int v:to[u])if(!vis[v]) dfs(v);
}
int lca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
for(int i=11;i>=0;--i)if(dep[fa[x][i]]>=dep[y]) x=fa[x][i];
if(x==y) return x;
for(int i=11;i>=0;--i)if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
int main(){
int n=read<int>();
for(int i=1;i<=n;++i){
int u=read<int>()+1,v=read<int>()+1;
to[u].push_back(v),to[v].push_back(u);
++deg[u],++deg[v];
}
deque<int> Q;
for(int u=1;u<=n;++u)if(deg[u]==1) Q.push_back(u);
while(Q.size()){
int u=Q.front();Q.pop_front();
vis[u]=-1;
for(int v:to[u])if(--deg[v]==1) Q.push_back(v);
}
for(int u=1;u<=n;++u)if(deg[u]==2){
dfs(u);break;
}
for(int u=1;u<=n;++u)if(vis[u]>0){
top[u]=u,dep[u]=1;
Q.push_back(u);
while(Q.size()){
int u=Q.front();Q.pop_front();
for(int i=1;i<=11;++i) fa[u][i]=fa[fa[u][i-1]][i-1];
for(int v:to[u])if(vis[v]==-1 and v!=fa[u][0]){
top[v]=top[u],fa[v][0]=u,dep[v]=dep[u]+1;
Q.push_back(v);
}
}
}
LD ans=0;
for(int u=1;u<=n;++u)for(int v=1;v<=n;++v){
if(top[u]==top[v]){
int f=lca(u,v);
ans+=(LD)1/(dep[u]+dep[v]-2*dep[f]+1);
}
else{
int x=dep[u]-dep[top[u]]+1+dep[v]-dep[top[v]]+1;
int y=abs(vis[top[u]]-vis[top[v]])-1,z=len-2-y;
ans+=(LD)1/(x+y)+(LD)1/(x+z)-(LD)1/(x+y+z);
}
}
printf("%.9Lf\n",ans);
return 0;
}