zoukankan      html  css  js  c++  java
  • P5405-[CTS2019]氪金手游【树形dp,容斥,数学期望】

    前言

    话说在(Loj)下了个数据发现这题的名字叫(fgo)

    在这里插入图片描述


    正题

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


    题目大意

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

    (1leq nleq 1000,0leq p_{i,j}leq 10^6)


    解题思路

    只考虑外向树的话就是水题了,因为显然的(x)要排在子树最前面的概率就是(frac{w_x}{sum_{yin subtree_x}w_y})

    然后直接(n^2)(dp)就可以力。

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

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

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

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

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


    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
    ",(ans+P)%P);
    	return 0;
    }
    
  • 相关阅读:
    IE浏览器cookie存放在本地什么目录
    C# clickonce 部署自定义文件
    [C#.Net]C#连接Oracle数据库的方法
    Eclipse 插件一览
    登录名 '***' 拥有一个或多个数据库。在删除该登录名之前,请更改相应数据库的所有者。 (Microsoft SQL Server,错误: 15174)
    SqlServer_表结构查询_查询当前用户所有表名
    sqlserver怎么删除有外键关联的数据?
    springboot logback控制台中文输出乱码
    控制台运行JAVA的jar文件时乱码 / Gradle编译设置编码格式
    Python 算法之一
  • 原文地址:https://www.cnblogs.com/QuantAsk/p/14974373.html
Copyright © 2011-2022 走看看