【概率与期望】【组合计数】TopCoder SRM 561 Orienteering

题意:

给出一颗树,树上有些选中点 C C C,现在随机从中选择一个大小为 k k k的子集 S S S,计算出一条包含所有 S S S中的点的最短路径 P P P,求 P P P的期望长度。

点数 N ≤ 2500 , k ≤ ∣ C ∣ ≤ 300 N\le2500,k\le |C|\le 300 N2500,kC300


分析:

每个 S S S中的点在路径 P P P第一次被访问的顺序为:
v 0 , v 1 , v 2 … … v ∣ S ∣ − 1 v_0,v_1,v_2……v_{|S|-1} v0,v1,v2vS1

性质1:

显然,路径 P P P的起点必为 v 0 v_0 v0,终点必为 v ∣ S ∣ − 1 v_{|S|-1} vS1
并且, P P P是由 ( v 0 − > v 1 ) , ( v 1 − > v 2 ) , ( v 3 − > v 4 ) … … ( v ∣ S ∣ − 2 − > v ∣ S ∣ − 1 ) (v_0->v_1),(v_1->v_2),(v_3->v_4)……(v_{|S|-2}->v_{|S|-1}) (v0>v1),(v1>v2),(v3>v4)(vS2>vS1)组成。其中 ( u − > v ) (u->v) (u>v)表示从u到v的最短路径。
证明:因为是第一次出现的顺序,所以若不是最短路径,则最短路径一定更优。

性质2:

对于任意一条边 e e e,其出现在 P P P中的充要条件为: e e e分开的两个联通块中,均有点 x ∈ S x\in S xS
证明略。

性质3:

对于任意一条边 e ∈ P e\in P eP,其最多出现2次。
证明略。

性质4:

对于任意一条边 e ∈ P e\in P eP,其出现一次的充要条件为: e ∈ ( v 0 − > v ∣ S ∣ − 1 ) e\in(v_0->v_{|S|-1}) e(v0>vS1)
证明:如果把这条路径连上,则形成了一条回路,回路中每条边都一定访问2次。由性质1可得,这条回路从 v ∣ S ∣ − 1 v_{|S|-1} vS1回到 v 0 v_0 v0的过程中,一定走的是最短路。现在把这条 ( v ∣ S ∣ − 1 − > v 0 ) (v_{|S|-1}->v_0) (vS1>v0)路径删去,这条路径上的边就只走了一次。

现在,有了这么一堆性质,很容易发现:对于一个固定的 S S S(随机出来的大小为k的 C C C的子集),其最短路径 P P P的长度为: 2 × ∣ E s ∣ − p a t h ( a − > b ) 2\times |E_s|-path(a->b) 2×Espath(a>b)
其中 E s E_s Es是一定存在于路径 P P P上的边的集合(即满足性质2的边的集合)。
p a t h ( a − > b ) path(a->b) path(a>b)是从 a a a b b b的最短路。

∣ E s ∣ |E_s| Es是固定的,因此要使得 p a t h ( a − > b ) path(a->b) path(a>b)尽可能大,即 ( a , b ) (a,b) (a,b) S S S中的最远点对。

根据期望的性质,对于这两个部分,可以分开考虑其期望贡献。(即分别算出 E ( ∣ E s ∣ ) E(|E_s|) E(Es) E ( p a t h ( a − > b ) ) E({path(a->b)}) E(path(a>b))

计算 E ( ∣ E s ∣ ) E(|E_s|) E(Es)

显然要考虑算每条边的贡献。

E ( ∣ E s ∣ ) = ∑ p e ∗ 1.0 E(|E_s|)=\sum p_e*1.0 E(Es)=pe1.0 p e p_e pe e ∈ E s e\in E_s eEs的概率)

考虑 e e e两侧的联通块 A A A, B B B

显然 p ( e ̸ ∈ E s ) = ( ∣ C ∣ ∣ A ∣ ) + ( ∣ C ∣ ∣ B ∣ ) ( ∣ C ∣ ∣ S ∣ ) p(e\not\in E_s)=\frac {\left( _{|C|}^{|A|}\right)+\left( _{|C|}^{|B|}\right)} {\left( _{|C|}^{|S|}\right)} p(e̸Es)=(CS)(CA)+(CB)

那么 p ( e ∈ E s ) = 1 − ( ∣ C ∣ ∣ A ∣ ) + ( ∣ C ∣ ∣ B ∣ ) ( ∣ C ∣ ∣ S ∣ ) p(e\in E_s)=1-\frac {\left( _{|C|}^{|A|}\right)+\left( _{|C|}^{|B|}\right)} {\left( _{|C|}^{|S|}\right)} p(eEs)=1(CS)(CA)+(CB)

所以,可以通过计算 e e e两侧被选中点的个数,计算 e e e出现在 E s E_s Es中的概率,

具体方法可以利用树形DP,计算组合数复杂度为 O ( K ) O(K) O(K)。这部分总复杂度控制在 O ( N K ) O(NK) O(NK)左右即可。

计算 E ( p a t h ( a − > b ) ) E(path(a->b)) E(path(a>b))

显然,如果 p a t h ( a − > b ) path(a->b) path(a>b)有贡献,那么 p a t h ( a − > b ) path(a->b) path(a>b)必然是 E s E_s Es所构成的子树的直径。

根据直径的经典性质:(a->b)是直径的充要条件为 ∀ v ∈ S , 满 足 l e n ( v , a ) &lt; l e n ( a , b ) , l e n ( v , b ) &lt; l e n ( a , b ) \forall v\in S,满足len(v,a)&lt;len(a,b),len(v,b)&lt;len(a,b) vSlen(v,a)<len(a,b),len(v,b)<len(a,b)

利用这个性质,我们可以直接枚举所有满足这个条件的点集 F ( F ⊂ C ) F(F\sub C) FFC

E ( p a t h ( a − &gt; b ) ) = l e n ( a , b ) ∗ ( ∣ F ∣ ∣ S ∣ ) ( ∣ C ∣ ∣ S ∣ ) E(path(a-&gt;b))=len(a,b)*\frac {\left( _{|F|}^{|S|}\right)} {\left( _{|C|}^{|S|}\right)} E(path(a>b))=len(a,b)(CS)(FS)

可以用N次bfs,预处理出来每两点之间的最短距离,然后枚举的复杂度是 O ( ∣ C ∣ 3 ) O(|C|^3) O(C3),可以接受。


#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iostream>
#include<vector>
#include<string>
#define SF scanf
#define PF printf
#define MAXN 2510
#define MAXC 310
using namespace std;
vector<string> s;
int n,m,tot,sum,totC,K;
vector<int> C;
vector<int> a[MAXN];
int dist[MAXC][MAXC];
int w[6][4]={{0,1},{1,0}};
double count(int x,int y){
	double ans=1;
	for(int i=0;i<K;i++)
		ans=ans*1.0*(x-i)/(1.0*(y-i));
	return ans;
}
pair<int,int> l[MAXN];
bool vis[MAXN];
int id[MAXN];
int dfs(int x,int fa){
	int res=(id[x]>0);
	for(int i=0;i<int(a[x].size());i++){
		int u=a[x][i];
		if(u==fa)
			continue;
		res+=dfs(u,x);	
	}
	return res;
}
double solve_edge(){
	double res=0;
	for(int i=1;i<=m;i++){
		int x=l[i].first;
		int y=l[i].second;
		int s1=dfs(x,y);
		int s2=totC-s1;	
		res+=1.0;
		if(s1>=K)
			res-=count(s1,totC);
		if(s2>=K)
			res-=count(s2,totC);
	}
}
bool bigger(int x,int y,int i,int j){
	if(i>j)
		swap(i,j);
	if(dist[x][y]!=dist[i][j])
		return dist[x][y]>dist[i][j];
	if(x!=i)
		return x>i;
	return y>j;	
}
double solve_road(){
	double res=0;
	for(int i=1;i<=totC;i++)
		for(int j=i+1;j<=totC;j++){
			int len=dist[i][j];
			int F=0;
			for(int k=1;k<=n;k++)
				if(k!=i&&k!=j&&bigger(i,j,k,i)&&bigger(i,j,k,j))
					F++;
			if(F>=K-2)
				res+=double(len)*count(F+2,totC)*double(K*(K-1))/double((F+2)*(F+1));
			PF("{%lf}\n",res);
		}
	return res;
}
void add_edge(int x,int y){
	l[++sum]=make_pair(x,y);
	a[x].push_back(y);
	a[y].push_back(x);
}
void dfs(int x,int rt,int len){
	vis[x]=1;
	if(id[x]!=0)
		dist[rt][id[x]]=len;
	for(int i=0;i<int(a[x].size());i++)
		if(vis[a[x][i]]==0)
			dfs(a[x][i],rt,len+1);
}
int main(){
	SF("%d%d",&n,&m);
	s.resize(n);
	for(int i=0;i<n;i++)
		cin>>s[i];
	SF("%d",&K);
	for(int i=0;i<n;i++)
		for(int j=0;j<m;j++){
			if(s[i][j]!='#')
				tot++;
			else
				continue;
			if(s[i][j]=='*'){
				C.push_back(i*m+j+1);
				id[i*m+j+1]=C.size();
			}
			for(int k=0;k<2;k++){
				int i1=i+w[k][0];
				int j1=j+w[k][1];
				if(i1>=0&&j1>=0&&i1<n&&j1<m&&s[i1][j1]!='#')
					add_edge(i*m+j+1,i1*m+j1+1);
			}
		}
	totC=C.size();
	memset(dist,0x3f,sizeof dist);
	for(int i=0;i<totC;i++){
		memset(vis,0,sizeof vis);
		dfs(C[i],i+1,0);
	}
	for(int k=1;k<=totC;k++)
		for(int i=1;i<=totC;i++)
			for(int j=1;j<=totC;j++)
				dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);
	n=tot;
	m=sum;
	double x=solve_edge();
	double y=solve_road();
	PF("%lf",2.0*x-y);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值