bzoj3167 [Heoi2013]Sao(树形DP)

83 篇文章 0 订阅
53 篇文章 0 订阅

bzoj3167 [Heoi2013]Sao

原题地址http://www.lydsy.com/JudgeOnline/problem.php?id=3167

题意:
WelcometoSAO(StrangeandAbnormalOnline)。这是一个VRMMORPG,含有n个关卡。但是,挑战不同关卡的顺序是一
个很大的问题。有n–1个对于挑战关卡的限制,诸如第i个关卡必须在第j个关卡前挑战,或者完成了第k个关卡才
能挑战第l个关卡。并且,如果不考虑限制的方向性,那么在这n–1个限制的情况下,任何两个关卡都存在某种程
度的关联性。即,我们不能把所有关卡分成两个非空且不相交的子集,使得这两个子集之间没有任何限制。

数据范围
数据组数 T≤5,1≤n≤1000

题解:
和bzoj4033很像,可以看看这篇 树形DP进阶:一类非线性的树形DP问题(例题 BZOJ4403 BZOJ3167)

题意就是:
有n个位置,n-1个限制,形如:i > j 或 i < j,表示第i个位置的数小于/大于第j个位置的数,求满足条件的n的排列数。答案mod 1000000007。

真没想到是树形DP…

看起来像是n^2的树形DP,状态的转移大抵与子树大小相关,要考虑到子树合并带来的影响。

dp[i][j]表示当前i的子树中,i之前有j个比i小的数的方案数。
边界dp[i][0]=1
考虑转移。
对于要合并rt的子树u,
1、 rt>u
dp[rt][i+j]+=dp[rt][i]j1k=0dp[u][k]C(i+jj)C(size[rt]+size[u]ij1size[u]j)
j1x=0dp[u][k] 是因为在除了u外j-1个比rt小的数与u的大小关系不确定,故k取0~j-1皆可。
两个组合数分别定了新增j个小的插进去的顺序和,size[u]-j个大的插进去的顺序。
2、rt < u
dp[rt][i+j]+=dp[rt][i]size[u]1k=jdp[u][k]C(i+jj)C(size[rt]+size[u]ij1size[u]j)

更新是要覆盖掉原来的内容,所以要用辅助数组。

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1005;
const int mod=1000000007;
int T,n,head[N],to[2*N],nxt[2*N],w[2*N],num=0,C[N][N],size[N],sum[N][N],dp[N][N],f[N];
void init()
{
    memset(head,0,sizeof(head)); num=0;
    memset(dp,0,sizeof(dp));
    memset(sum,0,sizeof(sum));
}
void build(int u,int v,int ww)
{
    num++;
    to[num]=v;
    w[num]=ww;
    nxt[num]=head[u];
    head[u]=num;
}
void dfs(int u,int fa)
{
    size[u]=1; dp[u][0]=1; 
    for(int ii=head[u];ii;ii=nxt[ii])
    {
        int v=to[ii]; if(v==fa) continue;
        dfs(v,u);
        for(int i=0;i<=size[u]+size[v];i++) f[i]=0;
        if(w[ii]<0)
        {
            for(int i=size[u]-1;i>=0;i--)   
            for(int j=size[v];j>=0;j--)
            {
                int ret=1LL*dp[u][i]*sum[v][j-1]%mod;
                ret=1LL*(1LL*ret*C[i+j][j]%mod)*C[size[u]+size[v]-i-j-1][size[v]-j]%mod;
                f[i+j]=(f[i+j]+ret)%mod;
            }
        }
        else
        {
            for(int i=size[u]-1;i>=0;i--) 
            for(int j=size[v]-1;j>=0;j--)
            {
                int sm=j==0?sum[v][size[v]-1]:(sum[v][size[v]-1]-sum[v][j-1]+mod)%mod;
                int ret=1LL*dp[u][i]*sm%mod;
                ret=1LL*(1LL*ret*C[i+j][j]%mod)*C[size[u]+size[v]-i-j-1][size[v]-j]%mod;
                f[i+j]=(f[i+j]+ret)%mod;
            }
        }
        size[u]+=size[v];
        for(int i=0;i<=size[u];i++) dp[u][i]=f[i];
    }
    sum[u][0]=dp[u][0];
    for(int i=1;i<size[u];i++) sum[u][i]=(sum[u][i-1]+dp[u][i])%mod;
}
int main()
{
    scanf("%d",&T);
    for(int i=0;i<=1000;i++)
    for(int j=0;j<=i;j++)
    {
        if(j==0||i==j) C[i][j]=1;
        else C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
    }
    while(T--)
    {
        scanf("%d",&n); init();
        for(int i=1;i<n;i++)
        {
            int u,v,ww; char opt[5];
            scanf("%d%s%d",&u,opt,&v);
            ww= opt[0]=='<'? 1:-1;
            build(u,v,ww); build(v,u,-ww);
        }
        dfs(1,1);   
        printf("%d\n",sum[1][n-1]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值