#4378 【2018提高测试】玩具

今日份难得智商在线(居然在讲评的时候听懂了概率dp ~~o(≧v≦)o)

这道题真的不错啊(当然大佬们一定觉得这是一道水题

Analysis

预处理dp[i][j]表示i个点的森林,有j个点在第1棵树的概率,
我们有状态转移方程 d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] ∗ ( j − 1 ) ∗ i n v [ i ] + d p [ i − 1 ] [ j ] ∗ ( i − j ) ∗ i n v [ i ] dp[i][j]=dp[i−1][j−1]∗(j−1)∗inv[i]+dp[i−1][j]∗(i−j)∗inv[i] dp[i][j]=dp[i1][j1](j1)inv[i]+dp[i1][j](ij)inv[i]
(inv[i]=1/i)

Q:为什么是j-1/i?
A:因为要抛开第一棵树的根节点。如果将根节点取走,则会多得一棵树

令 f[i][j] 表示有 i 个点的树,深度不超过 j 的概率
g[i][j] 表示有 i 个点的森林,深度不超过 j 的概率

f[i][j]直接从g[i−1][j−1]转移来;
g[i][j]考虑枚举第1棵树的大小 k,我们有状态转移方程:
g [ i ] [ j ] = ∑ k = 1 i f [ k ] [ j ] ∗ g [ i − k ] [ j ] ∗ d p [ i ] [ k ] g[i][j] = \sum_{k=1}^i f[k][j] ∗ g[i − k][j] ∗ dp[i][k] g[i][j]=k=1if[k][j]g[ik][j]dp[i][k]
最后只要 f [ n ] [ j ] − f [ n ] [ j − 1 ] f[n][j]−f[n][j−1] f[n][j]f[n][j1]就可以得到深度为j的树的概率

时间复杂度:O(n3)


Code
#include<bits/stdc++.h>
#define in read()
#define re register
#define int long long
using namespace std;
inline int read(){
	char ch;int f=1,res=0;
	while((ch=getchar())<'0'||ch>'9') if(ch=='-') f=-1;
	while(ch>='0'&&ch<='9'){
		res=(res<<1)+(res<<3)+(ch^48);
		ch=getchar();
	}
	return f==1?res:-res;
}
const int N=205;
int dp[N][N],f[N][N],g[N][N];
int n,p,inv[205],res[N];
inline int ksm(int a,int b){
	int res=1;
	while(b){
		if(b&1) res=res*a%p;
		a=a*a%p;
		b>>=1;
	}
	return res;
}
inline void init(){
	inv[1]=1;
	for(re int i=2;i<=n;++i) inv[i]=ksm(i,p-2);
	memset(f,-1,sizeof(f));
	memset(g,-1,sizeof(g));
}
inline int solve1(int x,int h);
inline int solve2(int x,int h){
	if(h<0) return 0;
	if(x==0) return 1;
	if(g[x][h]!=-1) return g[x][h];
	g[x][h]=0;
	for(re int i=1;i<=x;++i)
		g[x][h]=(solve1(i,h)%p*solve2(x-i,h)%p*dp[x][i]%p+g[x][h])%p;
	return g[x][h];
}
inline int solve1(int x,int h){
	if(f[x][h]!=-1) return f[x][h];
	f[x][h]=solve2(x-1,h-1);//f[i][j]-->g[i-1][j-1]
	return f[x][h];
}
signed main(){
	n=in;p=in;
	if(n==1){printf("0");return 0;	}
	init();
	dp[1][1]=1;
	for(re int i=2;i<=n;++i)
		for(re int j=1;j<=n;++j)
			dp[i][j]=(dp[i-1][j-1]*(j-1)%p*inv[i]%p+dp[i-1][j]*(i-j)%p*inv[i]%p)%p;
	f[1][1]=1;
	for(re int i=2;i<=n;++i) res[i]=solve1(n,i);//选n个点做一棵树,深度不超过i的概率
	res[1]=0;int ans=0;
	for(re int i=2;i<=n;++i)  ans=(ans+(res[i]-res[i-1])%p*(i-1)%p)%p;//长度=深度-1 
	cout<<(ans%p+p)%p;
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值