【BZOJ3167】[HEOI2013]SAO(动态规划)

【BZOJ3167】[HEOI2013]SAO(动态规划)

题面

BZOJ
洛谷

题解

显然限制条件是一个\(DAG\)(不考虑边的方向的话就是一棵树了)。
那么考虑树型\(dp\),设\(f[i][j]\)表示当前点\(i\)在其子树内的排名为\(j\)的方案数。
每次考虑加入一棵子树,即考虑把\(f[v][k]\)加入到\(f[u][i]\)的贡献中。
分类讨论,如果\(v\)应当在\(u\)之前,枚举\(v\)的子树内一共有多少个点在\(u\)之前,那么假设\(u\)当前的序列长度为\(x\)\(v\)当前的序列长度为\(y\),枚举一共有\(j\)\(v\)的子树内的点在\(u\)之前。
那么此时的方案数就是:\(f[u][i+j]\leftarrow {i+j-1\choose i-1}*f[u][i]*f[v][k]*{y-j+x-i\choose x-i}\)
然后我们来学习枚举(来写假的pascal代码)

for i:=1 to x do
    for k:=1 to y do 
        for j:=k to y do 
            f[u][i+j]+=f[u][i]*f[v][k]*C[i+j-1][i-1]*C[x+y-i-j][x-i]

似乎这个\(k\)只在\(f[v][k]\)中出现过???
那么我们换一下

for i:=1 to x do
    for j:=1 to y do 
        for k:=1 to j do 
            f[u][i+j]+=f[u][i]*f[v][k]*C[i+j-1][i-1]*C[x+y-i-j][x-i]

发现把\(f[v]\)数组前缀和一下底下这个玩意就可以做到\(O(1)\)转移啦。
而如果\(v\)要放在\(u\)之后也是类似的。
还是枚举有多少个放在\(u\)之前,那么这一部分的贡献是没变的,只是转移的时候的范围要变一变。

for i:=1 to x do
    for k:=1 to y do 
        for j:=0 to k-1 do 
            f[u][i+j]+=f[u][i]*f[v][k]*C[i+j-1][i-1]*C[x+y-i-j][x-i]

还是换一下

for i:=1 to x do
    for j:=0 to y do 
        for k:=i+1 to y do 
            f[u][i+j]+=f[u][i]*f[v][k]*C[i+j-1][i-1]*C[x+y-i-j][x-i]

这样子上面两层循环枚举的都是子树大小,因此全局总的复杂度就是\(O(n^2)\)啦。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MOD 1000000007
#define MAX 1010
void add(int &x,int y){x+=y;if(x>=MOD)x-=MOD;}
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
struct Line{int v,next,w;}e[MAX<<1];
int h[MAX],cnt;
inline void Add(int u,int v,int w){e[cnt]=(Line){v,h[u],w};h[u]=cnt++;}
void init(){memset(h,0,sizeof(h));cnt=1;}
int n,C[MAX][MAX];char ch[2];
int f[MAX][MAX],sz[MAX],tmp[MAX];
void dfs(int u,int ff)
{
    f[u][1]=1;sz[u]=1;
    for(int i=h[u];i;i=e[i].next)
    {
        int v=e[i].v;if(v==ff)continue;
        dfs(e[i].v,u);
        for(int a=1;a<=sz[u];++a)
            for(int b=0;b<=sz[v];++b)
                if(e[i].w)
                    add(tmp[a+b],1ll*f[u][a]*f[v][b]%MOD*C[a+b-1][a-1]%MOD*C[sz[u]+sz[v]-a-b][sz[u]-a]%MOD);
                else
                    add(tmp[a+b],1ll*f[u][a]*(f[v][sz[v]]-f[v][b]+MOD)%MOD*C[a+b-1][a-1]%MOD*C[sz[u]+sz[v]-a-b][sz[u]-a]%MOD);
        sz[u]+=sz[v];
        for(int j=1;j<=sz[u];++j)f[u][j]=tmp[j],tmp[j]=0;
    }
    for(int j=1;j<=sz[u];++j)add(f[u][j],f[u][j-1]);
}
int main()
{
    for(int i=0;i<MAX;++i)C[i][0]=1;
    for(int i=1;i<MAX;++i)
        for(int j=1;j<=i;++j)C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
    int T=read();
    while(T--)
    {
        init();n=read();
        for(int i=1,u,v;i<n;++i)
        {
            u=read()+1;scanf("%s",ch);v=read()+1;
            if(ch[0]=='<')Add(u,v,0),Add(v,u,1);
            else Add(u,v,1),Add(v,u,0);
        }
        dfs(1,0);
        printf("%d\n",f[1][n]);
    }
    return 0;
}

转载于:https://www.cnblogs.com/cjyyb/p/10485535.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值