BZOJ4675 点对游戏

传送门

题目大意

给定一棵$N$个点的树$(Nleq 5 imes 10^4)$,三个人轮流选点,每次等概率选一个之前没有被选过的点。

定义$M$个距离(简单路径上的边数)的点对$(Mleq 10)$是幸运的,求每个人分别获得的幸运点对数量的期望。

 

题解

首先不难发现每个人选的点是独立的且数量是确定的

假设选$k$个点,考虑每个点$x$对答案的贡献。设$P$表示$x$被选中的概率$=frac{k}{N}$

枚举$x$作为点对中前一个点,对答案的贡献是$sum P imes (k-1)frac{V}{N-1}$,其中$V$表示能与$x$组成幸运点对的数量。

化简之后得到$frac{k(k-1)}{N(N-1)}sum V$

不难发现$sum V$其实就是树上所有的有序幸运点对数量,自然而然的想到点分治,不过由于这里的边权之和路径边数有关,所以我们可以使用长链剖分在$O(NM)$的时间复杂度内解决。

假设无序幸运点对数量是$t$个,选出$k$个获得的幸运点对数量期望就是$frac{2tk(k-1)}{N(N-1)}$

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define LL long long
#define M 100020
using namespace std;
int read(){
	int nm=0,fh=1; int cw=getchar();
	for(;!isdigit(cw);cw=getchar()) if(cw=='-') fh=-fh;
	for(;isdigit(cw);cw=getchar()) nm=nm*10+(cw-'0');
	return nm*fh;
}
int n,m,fs[M],nt[M<<1],to[M<<1],tmp,D[12],t1,t2,t3;
int mxd[M],dep[M],dfn[M],mxs[M],cnt,F[M],tot,sum;
double m1,m2,m3,ans;
void link(int x,int y){nt[tmp]=fs[x],fs[x]=tmp,to[tmp++]=y;}
void dfs1(int x,int last){
	mxd[x]=0;
	for(int i=fs[x];i!=-1;i=nt[i]){
		if(to[i]==last) continue; dfs1(to[i],x);
		if(mxd[to[i]]>=mxd[x]) mxd[x]=mxd[to[i]]+1,mxs[x]=to[i];
	}
}
void dfs2(int x,int last){
	dfn[x]=++cnt,F[dfn[x]]=1; if(mxs[x]) dfs2(mxs[x],x);
	for(int i=1;i<=m;i++) if(D[i]<=mxd[x]) tot+=F[dfn[x]+D[i]];
	for(int i=fs[x];i!=-1;i=nt[i]){
		int v=to[i];
		if(v==last||v==mxs[x]) continue; dfs2(v,x);
		for(int k=0;k<=mxd[v];k++)
			for(int j=1;j<=m;j++) if(D[j]>k&&D[j]-k-1<=mxd[x]) tot+=F[dfn[v]+k]*F[dfn[x]+D[j]-1-k];
		for(int k=0;k<=mxd[v];k++) F[dfn[x]+k+1]+=F[dfn[v]+k];
	}
}
int main(){
	n=read(),m=read(),memset(fs,-1,sizeof(fs));
	for(int i=1;i<=m;i++) D[i]=read();
	for(int i=1;i<n;i++){int x=read(),y=read();link(x,y),link(y,x);}
	t1=t2=t3=n/3,t1+=(n%3>0),t2+=(n%3>1);
	m1=(t1*(t1-1)*1.0)/2.0,m2=(t2*(t2-1)*1.0)/2.0,m3=(t3*(t3-1)*1.0)/2.0;
	dfs1(1,0),dfs2(1,0),ans=tot*2.0/((1.0*n)*(1.0*(n-1)));
	printf("%.2f
%.2f
%.2f
",m1*ans,m2*ans,m3*ans);
	return 0;
}

  

原文地址:https://www.cnblogs.com/OYJason/p/9760377.html