zoukankan      html  css  js  c++  java
  • [HEOI2013]SAO ——计数问题

    题目大意:

    Welcome to SAO ( Strange and Abnormal Online)。这是一个 VR MMORPG, 含有 n 个关卡。但是,挑战不同关卡的顺序是一个很大的问题。

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

    对于每个数据,输出一行一个整数,为攻克关卡的顺序方案个数,mod 1,000,000,007 输出。

    题目翻译:

    发现最后一句话就是说:这是一棵树形图。

    所以我们现在有了一棵树,只是边是有向边,挑战的限制就是边的方向,我们必须把所有指向x0的关卡全部通过,才能通过x0关卡。

    其实,所有指向x0的边就是它的度数,所以可以看出来,

    这个题是让我们求这个树形图有多少种拓扑序。

    分析:

    这个题即使看了题解也是理解了半天。网上题解也不是很多,做法类似。

    树形计数问题,可以用树形DP,首先我们可以先尝试定义一维,定义f[i]表示以i为根的子树的拓扑序有多少种,现在我们需要考虑怎样将若干个儿子的值转移到父亲上。

    发现,如果把两个儿子的拓扑序,看做是两个区间,那么我们做的其实是一个区间合并的操作。

    但是由于边其实是有向的,(虽然我们是无向边建树)实际边的方向还决定父亲,该儿子的真正完全的拓扑序谁在前,谁在后。就是说,要先过了父亲,还是先过了儿子。

    非常无从下手的感觉。我们需要再定义一维。

    于是我们这样定义:

    f[i][j]表示,在以i为根的子树中,根节点i排在第j位的拓扑序的种类数。(其实所有的拓扑序就是f[i][1-size])

    可以发现,j不同时,方案数一定是独立的。

    现在我们要考虑转移:

    当我们循环到x的一个儿子y的时候,size[x]记录的是当前x与其前面所有儿子子树的size和,就是还没有包括y

    那么前面说了,就是一个区间合并,我们以x的位置作为断点考虑合并。

    先分类(因为我们无向边建树,但是实际上是有向边。)

    ①x<y 即先通过x,再通过y。

    这个时候,拓扑序合并后x的排名一定在y的前面。

    对于f[x][k],最终x前面有k-1个元素。可以从f[x][i](1<=i<=min(k,size))和 f[y][j](j的范围随后再确定)转移过来。转移之后,区间内共有size[x]+size[y]个数

    就是说,我在合并后的拓扑序中,先从之前的f[x][i]中的方案数中拿出若干种,放进大区间里,再从f[y][j]里选择一些方案数,放进大区间里。所以这里i一定小于等于k

    前k-1个位置,从之前的数中先挑出i-1个位置,有C(k-1,i-1)种选法,

    后size[x]+size[y]-k个位置(不算x), 已经选择了i-1个数,还剩下size[x]-i个数(x自己不算),有C(size[x]+size[y]-k,size[x]-i)种选法。

    再乘上每个选上的集合中自己的变化,也就是f[x][i]自己本身(类似多重集合的排列)

    剩下的位置就是f[y][j]的了,不需要再乘组合数,只需乘上f[y][j]就好。

    现在我们要确定j的取值范围:

    对于x<y的情况,x之前的数,我们已经填了i-1个位置,还剩下k-i个位置要填,

    为了使得y在x的后面,而y之前还能放j-1个数,所以要使得:j-1>=k-i,当然j<=size[y]

    所以,j的循环范围是,k-i+1<=j<=size[y]

    所以,对于x<y的情况,我们可以列出状态转移的方程是:

    f[x][k]=(1<=i<=min(k,size[x]))(k-i+1<=j<=size[y]) f[x][i]*c[k-1][i-1]*c[size[x]+size[y]-k][size[x]-i]*f[y][j]

    这样子,发现每次要循环一遍j,复杂度是O(n^4)的,直接挂掉。。。

    又发现,对于同一个y,我们好像加的是同一些树,循环的是同一些j。。。

    我们把这个式子用乘法分配律提出来一下:

    f[x][k]=(1<=i<=min(k,size[x])) f[x][i]*c[k-1][i-1]*c[size[x]+size[y]-k][size[x]-i]*(f[y][k-i+1]+...f[y][size[y]])

    所以,加粗部分是可以通过一个前缀合优化处理的,复杂度变成O(1)。

    ①x>y 即先通过y,再通过x。

    其实是同理的。f[x][i](1<=i<=min(k,size)),i的范围没有变。

    但是由于要保证y在x的前面,j-1个元素,必然不能填满k-i个位置

    所以,j-1<k-i (注意是小于,不是小于等于,因为还有一个位置是y自己,所以要用j-1个位置填不满k-i个位置)并且j>=1

    所以这里的状态转移方程是:

    f[x][k]=(1<=i<=min(k,size[x]))(1<=j<=k-i) f[x][i]*c[k-1][i-1]*c[size[x]+size[y]-k][size[x]-i]*f[y][j]

    同理可以乘法分配律,前缀和优化。

    详见代码:

    #include<bits/stdc++.h>
    #define ull unsigned long long
    #define ll long long
    using namespace std;
    const int N=1000+10;
    const int mod=1e9+7;
    int n,t;
    struct node{
        int nxt,to,val;
    }bian[2*N];
    int head[N],cnt;
    void add(int x,int y,int z)
    {
        bian[++cnt].to=y;
        bian[cnt].nxt=head[x];
        bian[cnt].val=z;
        head[x]=cnt;
    }
    
    ull f[N][N],sumdp[N][N];
    ull c[N][N];
    int size[N];
    bool vis[N];
    void dfs(int x)
    {
        size[x]=1;
        f[x][1]=1;
        vis[x]=1;
        for(int o=head[x];o;o=bian[o].nxt)
        {
            int y=bian[o].to;
            if(!vis[y])
            {
            dfs(y);
            if(bian[o].val)//......x...y
            {
                for(int k=size[x]+size[y];k>=1;k--)
                {
                    ull sum=0;
                    for(int i=1;i<=min(size[x],k);i++)
                    {    
                        int l=k-i,r=size[y];
                        ull del=(sumdp[y][size[y]]+mod-sumdp[y][k-i])%mod;//前缀和差值 
                        if(l<r)
                        {
                        ull q=(f[x][i]*del)%mod,p=(c[k-1][i-1]*c[size[x]+size[y]-k][size[x]-i])%mod;
                        p*=q;p=p%mod;sum+=p;sum%=mod;//这里,必须四个数分别计算并取模,否则会爆long long 
                        }    
                    }
                    f[x][k]=sum;
                }
            }
            else//.........y...x
            {
                for(int k=size[x]+size[y];k>=1;k--)
                {
                    ull sum=0;
                    for(int i=1;i<=min(size[x],k-1);i++)
                    {
                        int r=min(size[y],k-i);
                        ull del=sumdp[y][r];
                        ull q=(f[x][i]*del)%mod,p=(c[k-1][i-1]*c[size[x]+size[y]-k][size[x]-i])%mod;
                        p*=q;p=p%mod;sum+=p;sum%=mod;
                    }
                    f[x][k]=sum;
                }
            }    
            size[x]+=size[y];
            }
        }
        for(int i=1;i<=size[x];i++)//处理完了x,赋值前缀和,以便后续使用 
            sumdp[x][i]=(sumdp[x][i-1]+f[x][i])%mod;    
    }
    
    void clear()//清空 
    {
        cnt=0;
        for(int i=0;i<=n;i++)
        {
            head[i]=0;vis[i]=0;
            size[i]=1;
            for(int j=0;j<=n;j++)
             sumdp[i][j]=0,f[i][j]=0;
        }
    }
    int main()
    {
        c[0][0]=1;
        for(int i=1;i<=1005;i++)
        {
            c[i][0]=1;
            for(int j=1;j<=i;j++)
             c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
        }//1000的范围,组合数打表 
        cin>>t;
        while(t)
        {
            scanf("%d",&n);
            clear();
            int x,y;
            char q[3];
            for(int i=1;i<=n-1;i++)
            {
                scanf("%d%s%d",&x,q,&y);
                x++,y++;//变成以1开始 
                if(q[0]=='<')
                {
                    add(x,y,1);
                    add(y,x,0);
                }
                else{
                    add(y,x,1);
                    add(x,y,0);
                }//建无向边,x,y距离是1,表示x<y 先过x后过 y 
            }
            dfs(1);
            ull ans=0;
            for(int i=1;i<=size[1];i++)
            {
                ans=(ans+f[1][i])%mod;
            }//方案数 
            printf("%llu
    ",ans);
            t--;
        }
        return 0;
    }

    基本思路和代码参考shadowice1984https://www.luogu.org/blog/ShadowassIIXVIIIIV/solution-p4099

    详细化了很多。

  • 相关阅读:
    P1131 [ZJOI2007]时态同步(树形dp)
    P2831 愤怒的小鸟(状压dp)
    bzoj2456 / P2397 yyy loves Maths VI (mode)
    P1879 [USACO06NOV]玉米田Corn Fields(状压dp)
    P1026 统计单词个数
    P2679 子串
    数据库合并工具 esql
    在游戏中实现语音聊天和语音转化成文字
    在游戏中实现语音聊天和语音转化成文字
    类型强转的那些坑
  • 原文地址:https://www.cnblogs.com/Miracevin/p/9044910.html
Copyright © 2011-2022 走看看