题意
给定一棵
n
n
个节点的树,每条边的权值为 之间的随机整数,求这棵树两点之间最长距离不超过
S
S
的概率。
1≤L≤10
1
≤
L
≤
10
1≤S≤2000
1
≤
S
≤
2000
思路
这种概率题以前没碰到过,现在碰到连暴力也不会打。其实样本点除以样本空间是最稳的暴力。从 1 1 与 均有一条边的特殊情况入手,比较显然需要一条边一条边添加。用 dpi d p i 保存最大边权为 i i 的合法情况的概率(也就是说不能存在大于 的另一条边)。而树的情况,也不就是变成了一个子树一个子树添加, dfs d f s 如下:
void dfs(int u,int f)
{
dp[u][0]=1;
EOR(i,G,u)
{
int v=G.to[i];
if(v==f)continue;
dfs(v,u);
memset(ad,0,sizeof(ad));
memset(tmp,0,sizeof(tmp));
FOR(j,0,L)
FOR(k,0,S-j)
ad[k+j]+=dp[v][k]/(L+1);
FOR(j,0,S)
FOR(k,0,S-j)
tmp[max(j,k)]+=dp[u][j]*ad[k];
FOR(j,0,S)dp[u][j]=tmp[j];
}
}
ad
a
d
数组表示新添加的子树
v
v
的 ,
tmp
t
m
p
是
dpu
d
p
u
的一个滚动(先把新的值整体赋回去)。
观察上述代码,发现用添加子树两层循环时间开销过大,考虑通过前缀和优化掉一层。可以先令
j≥k
j
≥
k
,然后乘上所有的
k∈[0,min{j,S−j}]
k
∈
[
0
,
m
i
n
{
j
,
S
−
j
}
]
的和,同理令
k
k
大,乘上所有 ,最后发现
j=k
j
=
k
的情况被算了两次,减掉一次即可。
代码
#include<bits/stdc++.h>
#define FOR(i,x,y) for(register int i=(x);i<=(y);++i)
#define DOR(i,x,y) for(register int i=(x);i>=(y);--i)
#define N 1003
typedef long long LL;
using namespace std;
template<const int maxn,const int maxm>struct Linked_list
{
int head[maxn],to[maxm],nxt[maxm],tot;
void clear(){memset(head,-1,sizeof(head));tot=0;}
void add(int u,int v){to[++tot]=v,nxt[tot]=head[u],head[u]=tot;}
#define EOR(i,G,u) for(register int i=G.head[u];~i;i=G.nxt[i])
};
Linked_list<N,N<<1>G;
double dp[N][2003],ad[2003],sumdp[2003],sumad[2003],tmp[2003];
int n,L,S;
void dfs(int u,int f)
{
dp[u][0]=1;
EOR(i,G,u)
{
int v=G.to[i];
if(v==f)continue;
dfs(v,u);
memset(tmp,0,sizeof(tmp));
memset(ad,0,sizeof(ad));
FOR(j,0,L)
FOR(k,0,S-j)
ad[k+j]+=dp[v][k]/(L+1);
sumad[0]=ad[0];
sumdp[0]=dp[u][0];
FOR(j,1,S)sumad[j]=sumad[j-1]+ad[j],sumdp[j]=sumdp[j-1]+dp[u][j];
FOR(j,0,S)
{
int k=min(j,S-j);
tmp[j]+=dp[u][j]*sumad[k];
}
FOR(k,0,S)
{
int j=min(k,S-k);
tmp[k]+=ad[k]*sumdp[j];
}
FOR(j,0,S/2)tmp[j]-=dp[u][j]*ad[j];
FOR(j,0,S)dp[u][j]=tmp[j];
}
}
int main()
{
G.clear();
scanf("%d%d%d",&n,&L,&S);
FOR(i,1,n-1)
{
int u,v;
scanf("%d%d",&u,&v);
G.add(u,v);
G.add(v,u);
}
dfs(1,0);
double ans=0;
FOR(i,0,S)ans+=dp[1][i];
printf("%.6lf\n",ans);
return 0;
}