JZOJ5233 【GDOI模拟8.5】概率博弈 树形dp

168 篇文章 0 订阅
12 篇文章 0 订阅

妈耶,改这题各种失误,清空都能清错,本来清x然后清空了儿子,服了。
区间题做多了下意识leaf[1]-i+1
服气。
Description

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

Input

输入文件名为game.in。
第一行包含一个整数n。
接下来n-1行,每行两个整数u,v,表示有一条连接节点u,v的边。

Output

输出文件名为game.out。
输出一个整数,表示答案。

Sample Input

输入1:
5
1 2
2 3
1 4
2 5

输入2:
10
10 7
7 6
10 2
2 3
3 8
3 1
6 9
7 5
1 4

Sample Output

输出1:
14

输出2:
74

Data Constraint

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

wwtdalao的题目。
据gjx大爷说是经典题= =,很常见的套路。
就是在树上dp,问了hhh才切,看来我还是很菜= =
首先贴个题解:
这里写图片描述
那么我们就可以直接dp了。
设f为图中的G。
那么我们先统计出每个节点为根的叶子结点个数,然后开始dp。
边界条件f[x][1][0]=f[x][0][1]=1,x为叶子。
对于当前结点x,枚举他的子节点(子树),进行合并。
枚举的第一个直接赋值,然后其他的合并分类讨论。
然后枚举i,j表示进行合并的两颗树中有多少个0,然后对于合并时相连的节点的0/1讨论。
如果dep[x]&1,由小A操作,那么只有x=0,y=0(连接的两个节点)时F[x]=0(注意是大F)否则都为1,因为是取max。
反之只有x=1,y=1的时候F[X]=1;因为是取min。
然后就可以直接更新了。
答案统计就是f[1][i][1]*fac[i]*fac[leaf[1]-i]
因为0和1的排列顺序不固定,所以用阶乘计算。

#include<cstdio>
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=5e3+5;
const int mo=1e9+7;
typedef long long ll;
int n,m;
int g[N][N][2],tot;
int fa[N],leaf[N],fac[N];
bool bo[N];
int f[N][N][2],head[N],next[N<<1],go[N<<1];
inline void add(int x,int y)
{
    go[++tot]=y;
    next[tot]=head[x];
    head[x]=tot;
}
inline void dfs1(int x)
{
    bool flag=1;
    for(int i=head[x];i;i=next[i])
    {
        int v=go[i];
        if (v!=fa[x])
        {
            flag=0;
            fa[v]=x;
            dfs1(v);
            leaf[x]+=leaf[v];
        }
    }
    if(flag)leaf[x]=1;
    bo[x]=flag;
}
inline void dfs(int x,int opt)
{
    if(bo[x])
    {
        f[x][1][0]=1;
        f[x][0][1]=1;
        return;
    }
    for(int i=head[x];i;i=next[i])
    {
        int v=go[i];
        if (v!=fa[x])
        {
            dfs(v,1-opt);
        }
    }
    int num=0,sum=0;
    for(int i=head[x];i;i=next[i])
    {
        int v=go[i];
        if (v!=fa[x])
        {
            num++;
            if (num==1)memcpy(f[x],f[v],sizeof(f[x]));
            else
            {
                fo(j,0,leaf[x])g[x][j][0]=g[x][j][1]=0;
                fo(k,0,leaf[v])
                {
                    fd(j,leaf[x],0)
                    {
                        if (j+k>leaf[x])continue;
                        if (opt==0)
                        {
                            g[x][j+k][0]=(ll)(g[x][j+k][0]+1ll*f[v][k][0]*f[x][j][0]%mo)%mo;
                            g[x][j+k][1]=(ll)(g[x][j+k][1]+1ll*f[v][k][0]*f[x][j][1]%mo)%mo;
                            g[x][j+k][1]=(ll)(g[x][j+k][1]+1ll*f[v][k][1]*f[x][j][0]%mo)%mo;
                            g[x][j+k][1]=(ll)(g[x][j+k][1]+1ll*f[v][k][1]*f[x][j][1]%mo)%mo;
                        } 
                        else
                        {
                            g[x][j+k][0]=(ll)(g[x][j+k][0]+1ll*f[v][k][0]*f[x][j][0]%mo)%mo;
                            g[x][j+k][0]=(ll)(g[x][j+k][0]+1ll*f[v][k][0]*f[x][j][1]%mo)%mo;
                            g[x][j+k][0]=(ll)(g[x][j+k][0]+1ll*f[v][k][1]*f[x][j][0]%mo)%mo;
                            g[x][j+k][1]=(ll)(g[x][j+k][1]+1ll*f[v][k][1]*f[x][j][1]%mo)%mo;
                        }
                    }
                }
                memcpy(f[x],g[x],sizeof(f[x]));
            }
            sum+=leaf[v];
        }
    }
}
int main()
{
    freopen("game.in","r",stdin);
    freopen("game.out","w",stdout);
    scanf("%d",&n);
    fo(i,1,n-1)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y);
        add(y,x);
    }
    dfs1(1);
    dfs(1,0);
    fac[0]=1;
    fo(i,1,N)fac[i]=1ll*fac[i-1]*i%mo;
    int ans=0;
    fo(i,0,leaf[1])
    ans=(ans+1ll*fac[i]*f[1][i][1]%mo*fac[leaf[1]-i]%mo)%mo;
    printf("%d\n",ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值