BZOJ3451 Normal 和 CF235D Graph Game

Normal

定义一次点分治的复杂度是所有分治中心分治时的子树大小之和。

给定一棵树,问所有点等概率被选做重心,点分治的期望复杂度。

(n leq 30000)

题解

https://www.cnblogs.com/SGCollin/p/10597925.html

根据期望的线性性,答案等价于每个点在点分树上的深度期望之和。

思路是从点对的角度考虑某一个点是否会产生贡献。

[E(dep_x)=sum_{y=1}^n P(xin subtree_y) ]

也就是 (x) 在点分树上在 (1dots n) 的子树中的概率和。

考虑点分树上 (y)(x) 的祖先的条件,要求 (x)(y) 构成的这条链上第一个在点分治过程中被删除的点是 (y) ,由于链上被选中的概率相等,因此这个概率为 (frac{1}{dis(x,y) + 1})

所以答案为

[sum_{x=1}^nsum_{j=1}^n frac{1}{dis(i,j) + 1}\ =sum_{i = 0}^n frac{cnt_i}{i + 1} ]

因此需要点分治求长度为 (i) 的路径条数 (cnt_i) ,注意到合并的时候是卷积的形式。

容斥做法

不考虑重复路径,把子树DFS一遍,直接自己和自己进行卷积,再去掉子树内重复计数的路径即可。

每一层最差以自己的size作为长度进行卷积,因此复杂度为 (O(nlog^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
",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
",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/autoint/p/12107764.html