JZOJ5233【GDOI模拟】概率博弈

87 篇文章 0 订阅
26 篇文章 0 订阅

Description

小A和小B在玩游戏。这个游戏是这样的:
有一棵n个点的以1为根的有根树,叶子有权值。假设有m个叶子,那么树上每个叶子的权值序列就是一个1->m 的排列。
一开始在1号点有一颗棋子。两人轮流将这颗棋子移向其当前位置的一个儿子。假如棋子到达叶子,游戏结束,最终获得的权值为所在叶子对应权值。
小A希望最后的权值尽量大,小B希望尽量小。小A是先手。
在玩了很多局游戏后,小B对其中绝大多数局游戏的结果不满意,他觉得是小A对叶子权值做了手脚。于是他一怒之下,决定将叶子的权值随机排列。现在小B想知道,假如叶子的权值是随机排列的(即叶子权值的每种排列都以等概率出现),那么游戏期望的结果是多少?
请输出答案乘上m! 对10^9+7取模的结果,显然这是一个整数。

Solution

这题有一个很套路的想法,就是我们考虑最后答案大于等于k的方案数,然后用k的方案数-k+1的方案数就是等于k的方案数。
那么大于等于k的方案数的讨论也有一个很套路的想法,就是对于所有大于等于k的权值设为1,其它设为0,然后向上合并,如果最后在根节点能合并出1,那么这种01染色方案就合法,然后再给0和1表上号就好了(0的个数的阶乘和1的个数的阶乘)
那么我们就设f[i][j][0,1]表示在第i个点,以它为根的子树的叶子节点填了j个0,当前这个点的颜色为0或1。
然后我们分层数来考虑,对于奇数层(从0层开始),那么它的儿子数有一个0,当前颜色就是0的,那么我们就用总方案-全都是1的个数来求,当前要是1的话,那么就把儿子的1做个背包,枚举前面儿子的叶子然后枚举当前要合并的儿子的叶子背包一下(这样是n^2的,相当于把所有的叶子两两之间匹配一下),对于偶数层也是一样的。
最后在根节点上恰好为k的方案,然后再标号就好了。
其实还有一种求答案的方法,我们要求的期望是 xP(x)
那么其实在一个平面图上就相当于1P(1),2P(2),3P(3),…nP(n),上面的方法是竖着求的,那么我们考虑横着求,设 P(x) 表示权值大于等于x的概率,那么答案就是 P(x) 和我们上面设的状态是一样的。

Code

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fod(i,a,b) for(i=a;i>=b;i--)
#define rep(i,a) for(i=first[a];i;i=next[i])
using namespace std;
typedef long long ll;
const int maxn=5007,mo=1e9+7;
ll i,j,k,l,t,n,m;
ll first[maxn*2],last[maxn*2],next[maxn*2],num,pp;
ll ni[maxn],fact[maxn],ans,f[maxn][maxn][2],son[maxn];
void add(int x,int y){
    last[++num]=y,next[num]=first[x],first[x]=num;
}
ll qsm(ll x,ll y){
    ll z=1;
    for(;y;y/=2,x=x*x%mo)if(y&1)z=x*z%mo;
    return z;
}
ll c(ll x,ll y){
    return fact[x]*ni[y]%mo*ni[x-y]%mo;
}
void dfs(int x,int y,int z){
    int i,l=0;
    rep(i,x){
        if(last[i]!=y){
            l++;dfs(last[i],x,z+1);
            fod(k,son[x]+son[last[i]],0){
                pp=0;
                fo(j,0,son[last[i]]){
                    if(k-j<0)break;
                    if(z%2)(pp+=f[last[i]][j][1]*f[x][k-j][1]%mo)%=mo;
                    else(pp+=f[last[i]][j][0]*f[x][k-j][0]%mo)%=mo;
                }
                if(z%2)f[x][k][1]=pp;else f[x][k][0]=pp;
                if(l==1){if(z%2)f[x][k][1]+=f[last[i]][k][1];else f[x][k][0]+=f[last[i]][k][0];}
            }
            son[x]+=son[last[i]];
        }
    }
    if(!l)son[x]=1,f[x][0][1]=f[x][1][0]=1;
    else{
        fo(j,0,son[x])if(z%2)f[x][j][0]=(c(son[x],j)-f[x][j][1]+mo)%mo;else 
        f[x][j][1]=(c(son[x],j)-f[x][j][0]+mo)%mo;
    }
}
int main(){
    freopen("game.in","r",stdin);
    freopen("game.out","w",stdout);
    fact[0]=ni[0]=1;fo(i,1,5000)fact[i]=fact[i-1]*i%mo;ni[5000]=qsm(fact[5000],mo-2);
    fod(i,4999,1)ni[i]=ni[i+1]*(i+1)%mo;
    scanf("%lld",&n);
    fo(i,1,n-1)scanf("%lld%lld",&k,&l),add(k,l),add(l,k);
    dfs(1,0,0);
    fo(i,0,son[1]){
        ans=(ans+f[1][i][1]*fact[i]%mo*fact[son[1]-i])%mo;
    }
    ans=(ans+mo)%mo;
    printf("%lld\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值