【JZOJ5233】【GDOI模拟8.5】概率博弈

67 篇文章 1 订阅
9 篇文章 0 订阅

Description

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

Data Constraint

对于10%的数据,n<=5
对于30%的数据,n<=10
对于60%的数据, n<=50
对于100%的数据,n<=5000,保证给出的是一棵合法的树。

Solution

我们假设A最后取的值是k,我们令大于等于k的值为1,其余为0,那么我们做个树形dp,设f[i][j][0..1]表示以i为根的子树中,有j个1,最后取到的值为0或1。那么对于小A取的值,我们直接算出f[i][j][0]= ∏f[son[x]][k][0],f[i][j][1]= Cjsize[i]f[i][j][0] 。对于小A取的值是k的方案即f[1][k][1]-f[1][k+1][1],由于答案是带标号的,所以我们最后要记入答案是(f[1][k][1]-f[1][k+1][1])k! (size[1]-k)!。

Code

#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn=5e3+5,mo=1e9+7;
ll f[maxn][maxn][2],first[maxn],last[maxn*2],next[maxn*2],size[maxn];
ll n,m,i,t,j,k,l,x,y,z,q,num,c[maxn],d[maxn],ans;
ll mi(ll x,ll y){
    if (y==1) return x;
    ll t=mi(x,y/2);
    if (y%2) return t*t%mo*x%mo;return t*t%mo;
}
ll make(int x,int y){
    return c[y]*mi(c[y-x],mo-2)%mo*mi(c[x],mo-2)%mo;
}
void dg1(int t,int x,ll sum,int p,int num){
    int i;
    if (t>d[0]){
        f[x][num][p]=(f[x][num][p]+sum)%mo;return;
    }
    for (i=0;i<=size[d[t]];i++)
        if (f[d[t]][i][p])dg1(t+1,x,sum*f[d[t]][i][p]%mo,p,num+i);
}
void dg(int x,int y,int p){
    int t;
    for (t=first[x];t;t=next[t]){
        if (last[t]==y)continue;
        dg(last[t],x,1-p);
        size[x]+=size[last[t]];
    }
    d[0]=0;
    for (t=first[x];t;t=next[t])
        if (last[t]!=y)d[++d[0]]=last[t];
    if (size[x]){
        dg1(1,x,1,p,0);
        for (j=0;j<=size[x];j++)
            f[x][j][1-p]=(make(j,size[x])-f[x][j][p]+mo)%mo;
    }else size[x]++,f[x][1][1]=f[x][0][0]=1; 
}
void lian(int x,int y){
    last[++num]=y;next[num]=first[x];first[x]=num;
}
int main(){
    freopen("game.in","r",stdin);freopen("game.out","w",stdout);
    scanf("%d",&n);
    for (i=1;i<n;i++)
        scanf("%d%d",&x,&y),lian(x,y),lian(y,x);
    c[0]=1;
    for (i=1;i<=n;i++)
        c[i]=c[i-1]*i%mo;
    dg(1,0,0);m=size[1];
    for (i=0;i<=m;i++)
        ans=ans+f[1][i][1]*c[i]%mo*c[m-i]%mo;
    ans=ans%mo;
    printf("%lld\n",ans);
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值