P5405-[CTS2019]氪金手游【树形dp,容斥,数学期望】

前言

话说在 L o j Loj Loj下了个数据发现这题的名字叫 f g o fgo fgo

P5405-[CTS2019]氪金手游【树形dp,容斥,数学期望】


正题

题目链接:https://www.luogu.com.cn/problem/P5405


题目大意

n n n张卡的权值为 1 / 2 / 3 1/2/3 1/2/3的概率权重分别是 p x , 1 / 2 / 3 p_{x,1/2/3} px,1/2/3​,然后按照权值每次获得一张未获得的卡,然后再该出一棵有向树(方向可以都是外向或内向的),求所有每条边 ( u , v ) (u,v) (u,v), u u u都比 v v v先获得的概率。

1 ≤ n ≤ 1000 , 0 ≤ p i , j ≤ 1 0 6 1\leq n\leq 1000,0\leq p_{i,j}\leq 10^6 1≤n≤1000,0≤pi,j​≤106


解题思路

只考虑外向树的话就是水题了,因为显然的 x x x要排在子树最前面的概率就是 w x ∑ y ∈ s u b t r e e x w y \frac{w_x}{\sum_{y\in subtree_x}w_y} ∑y∈subtreex​​wy​wx​​。

然后直接 n 2 n^2 n2的 d p dp dp就可以力。

但是现在有内向边怎么办,还是考虑转换成只有外向的,也就是去掉一种限制。

去掉一种限制的话容斥是一个不错的办法,考虑的话就是恰好若干条指定边(内向边),我们可以指定至少 k k k跳内向边不满足条件,这样就组成了一个外向森林,可以很容易处理出答案,而且这样的容斥系数就是 ( − 1 ) k (-1)^k (−1)k。

然后直接 d p dp dp就得了,设 f i , j f_{i,j} fi,j​表示到节点 i i i然后权值和是 j j j,如果限制一条内向边就直接乘上一个 − 1 -1 −1就好了。

额这种树形 d p dp dp枚举子树大小可以做到 n 2 n^2 n2这个是老生常谈了

时间复杂度 O ( n 2 ) O(n^2) O(n2)


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=1100,P=998244353;
struct node{
	ll to,next,w;
}a[N<<1];
ll n,tot,ans,ls[N],siz[N],w[N][3],f[N][3*N],g[N*3];
ll power(ll x,ll b){
	ll ans=1;
	while(b){
		if(b&1)ans=ans*x%P;
		x=x*x%P;b>>=1; 
	}
	return ans;
}
void addl(ll x,ll y,ll w){
	a[++tot].to=y;
	a[tot].next=ls[x];
	a[tot].w=w;
	ls[x]=tot;return;
}
void dp(ll x,ll fa){
	ll d=power(w[x][0]+w[x][1]+w[x][2],P-2);
	siz[x]=3;
	f[x][1]=w[x][0]*d%P;
	f[x][2]=w[x][1]*d*2ll%P;
	f[x][3]=w[x][2]*d*3ll%P;
	for(ll e=ls[x];e;e=a[e].next){
		ll y=a[e].to;
		if(y==fa)continue;
		dp(y,x);
		if(a[e].w){
			for(ll i=1;i<=siz[x];i++)
				for(ll j=1;j<=siz[y];j++)
					(g[i+j]-=f[x][i]*f[y][j]%P)%=P,(g[i]+=f[x][i]*f[y][j]%P)%=P;
		}
		else{
			for(ll i=1;i<=siz[x];i++)
				for(ll j=1;j<=siz[y];j++)
					(g[i+j]+=f[x][i]*f[y][j]%P)%=P;
		}
		siz[x]+=siz[y];
		for(ll i=1;i<=siz[x];i++)
			f[x][i]=g[i],g[i]=0;
	}
	for(int i=1;i<=siz[x];i++)
		f[x][i]=f[x][i]*power(i,P-2)%P;
	return;
}
signed main()
{
	scanf("%lld",&n);
	for(ll i=1;i<=n;i++)
		scanf("%lld%lld%lld",&w[i][0],&w[i][1],&w[i][2]);
	for(ll i=1;i<n;i++){
		ll x,y;scanf("%lld%lld",&x,&y);
		addl(x,y,0);addl(y,x,1);
	}
	dp(1,0);
	for(ll i=1;i<=siz[1];i++)
		(ans+=f[1][i])%=P;
	printf("%lld\n",(ans+P)%P);
	return 0;
}
上一篇:田忌赛马 题解


下一篇:0717练习赛 :: 平方数