题意
现在有一颗有n个节点的树(n<=64),每条边都会随机的从0~L中间取一个边权值(L<=8),求问生成的树任意两点间距离不超过S(S<=512)的概率是多少。
分析
涉及到树了。所以说看看就觉得是概率DP+树形DP的样子。
而且S,n,L都不算大。
状态的定义
然后树形DP的定义一般是涉及到以某个节点为根的子树的。然后这里dp里面存的肯定是概率,因为概率是实数基本上不可能作下标的。
还是好定义的。
dp[x][l] 表示以x为子树,子树内两两节点距离不超过S,且根到叶子节点的最长长度为l的概率是多大。
答案
∑i=0S dp[1][i]
∑
i
=
0
S
d
p
[
1
]
[
i
]
初始化
每个节点x,dp[x][0]=1.0
状态转移方程
一个节点可能会有多个子树,然后就会很麻烦…
Sn
S
n
的转移显然不可行。
首先来考虑只有两个子树的怎么转移。假设子树分别为to1,to2
dp[x][max(l1,l2)]+=dp[to1][l1]*dp[to2][l2]
然后这里显然可以用一个前缀和维护一下…
dp[x][l]=dp[to1][l]∗∑i=0ldp[to2][i] +dp[to2][l]∗∑i=0ldp[to1][i]−dp[to1][l]∗dp[to2][l]
d
p
[
x
]
[
l
]
=
d
p
[
t
o
1
]
[
l
]
∗
∑
i
=
0
l
d
p
[
t
o
2
]
[
i
]
+
d
p
[
t
o
2
]
[
l
]
∗
∑
i
=
0
l
d
p
[
t
o
1
]
[
i
]
−
d
p
[
t
o
1
]
[
l
]
∗
d
p
[
t
o
2
]
[
l
]
然后多颗子树怎么解决呢…就是把合并过的子树看成一颗子树,然后转移就好了。详细参见
code
#include<bits/stdc++.h>
#define pb push_back
#define db double
#define S 515
using namespace std;
void read(int &x){
x=0; char c=getchar();
for (; c<'0'; c=getchar());
for (; c>='0'; c=getchar())x=(x<<3)+(x<<1)+(c^'0');
}
db dp[65][513],f[513],sum1[513],sum2[513],p;
vector<int>e[65];
int ll,ss;
void dfs(int ff,int x){
int i,l,s;
dp[x][0]=1.0;
for (i=0; i<e[x].size(); i++)if (e[x][i]!=ff){
int to=e[x][i];
dfs(x,to);
memset(f,0,sizeof(f));
for (l=0; l<=ll; l++)for (s=0; l+s<=ss; s++)f[l+s]+=p*dp[to][s];
sum1[0]=f[0];
for (s=1; s<=ss; s++)sum1[s]=sum1[s-1]+f[s];
sum2[0]=dp[x][0];
for (s=1; s<=ss; s++)sum2[s]=sum2[s-1]+dp[x][s];
for (s=0; s<=ss; s++){
int mns=min(s,ss-s);
db tmp=dp[x][s]*f[s];
dp[x][s]=f[s]*sum2[mns]+dp[x][s]*sum1[mns];
if (s*2<=ss)dp[x][s]-=tmp;
}
}
}
int main(){
int ca,n;
read(ca);
int numb=0,i,x,y;
for (; numb<ca; ){
memset(dp,0,sizeof(dp));
read(n); read(ll); read(ss); p=1.0/(ll+1.0);
for (i=1; i<n; i++){read(x); read(y); e[x].pb(y); e[y].pb(x);}
dfs(0,1);
db res=0;
for (i=0; i<=ss; i++)res+=dp[1][i];
printf("Case %d: %.6f\n",++numb,res);
for (i=1; i<=n; i++)e[i].clear();
}
return 0;
}
反思
这题也没自己写出来…虽然想到了树形DP和状态定义但是在转移部分成功卡住。虽然说把两棵子树并在一起的操作以前也不是没写过但是还是一时半会儿没想出来。
总之,我太弱了。