【XSY3929】PQ 树(区间dp)

题面

PQ 树

题解

注意到题目中提到了 “合法的 PQ 树都能表示出 \(1,2,3,\cdots,n\) 这个排列"。那么如果我们把所有叶子节点按它们的数字按 \(1\sim n\) 从左到右排,形成的 PQ 树是没有相交边的,也就是说不会出现这种情况:

在这里插入图片描述

你也可以理解成一棵子树一定存着的是一段连续的数字区间(顺序不一定是有序的)。

不难发现如果此时两课 PQ 树的形态不同,他们能表示出来的集合也不同。

那我们考虑区间dp,设 \(f(i,j)\) 表示仅考虑 \([i,j]\) 中的数字,他们恰好形成一棵子树的方案数是多少。

你发现,如果 \([i,j]\) 形成了一棵子树,那么不管这棵子树上面再怎么连,\([i,j]\) 都一定是靠在一起的。

所以 \([i,j]\) 能形成一棵子树,当且仅当给出的 \(k\) 个排列中的 \([i,j]\) 都是靠在一起的。不妨称这样的 \([i,j]\) 是 “好的”。

然后再设 \(g(i,j)\) 表示仅考虑 \([i,j]\) 中的数字,他们形成 \(\geq 1\) 棵子树的方案数是多少。

那么对于 P 节点,转移方程就显而易见了(详见代码)。

而对于 Q 节点,我们的要求会更严格。

具体来说,如果 \([i,j]\) 形成了一棵以 Q 节点为根的子树。不妨设这个 Q 节点有若干个儿子 \(v_1,v_2,\cdots,v_m\)\(m\geq 3\)),那么我们不仅需要保证给出的 \(k\) 个排列中这 \(m\) 个儿子所代表的的数字区间是靠着的,还需要保证 \(k\) 个排列中这些数字区间是以正序(或反序)排列的。

不妨设 \(h(i,j)\) 表示仅考虑 \([i,j]\) 中的数字,他们形成 \(\geq 2\) 棵子树,且这些子树在 \(k\) 个排列中都是按正序(或反序)排序的。

转移也详见代码:

#include<bits/stdc++.h>
 
#define N 510
 
using namespace std;
 
namespace modular
{
    const int mod=1000000007;
    inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
    inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
    inline int mul(int x,int y){return 1ll*x*y%mod;}
}using namespace modular;
 
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=(x<<1)+(x<<3)+(ch^'0');
        ch=getchar();
    }
    return x*f;
}
 
int k,n,a[N],num[N][N];
int f[N][N],g[N][N],h[N][N];
//f[l,r]:[l,r]恰好构成一棵树
//g[l,r]:[l,r]构成>=1棵树
//h[l,r]:[l,r]构成>=2棵树,且分成的树在k个序列中按正/反序排序 
int sum[N][N];
bool good[N][N];
 
int main()
{
    k=read(),n=read();
    for(int i=1;i<=k;i++)
    {
        for(int j=1;j<=n;j++) a[j]=read();
        for(int l=1;l<=n;l++)
        {
            int minn=a[l],maxn=a[l];
            for(int r=l;r<=n;r++)
            {
                minn=min(minn,a[r]);
                maxn=max(maxn,a[r]);
                if(maxn-minn+1==r-l+1) num[minn][maxn]++;
            }
        }
    }
    for(int l=1;l<=n;l++)
    {
        for(int r=l;r<=n;r++)
        {
            good[l][r]=(num[l][r]==k);
            sum[l][r]=sum[l][r-1]+good[l][r];
        }
    }
    for(int l=n;l>=1;l--)
    {
        for(int r=l;r<=n;r++)
        {
            if(l==r)
            {
                f[l][r]=g[l][r]=1;
                continue;
            }
            if(!good[l][r])
            {
                for(int i=l;i<r;i++)
                    g[l][r]=add(g[l][r],mul(f[l][i],g[i+1][r]));
                continue;
            }
            for(int i=l;i<r;i++)
            {
                if(good[l][i])
                {
                    f[l][r]=add(f[l][r],mul(f[l][i],g[i+1][r]));
                    g[l][r]=add(g[l][r],mul(f[l][i],g[i+1][r]));//>=2
                    h[l][r]=add(h[l][r],mul(f[l][i],f[i+1][r]));//=2//这里本来应该有一个前提要求good[l][r]=1,但前面判断过就舍去了 
                    if(sum[l][r-1]-sum[l][i]>=1)
                    //这里的意思是good[l][i+1~r-1]至少有一个值是1,这样就能保证[l,i]这棵子树和第二棵子树是连在一起的(也就是正序或反序)
                    //good[l][r]=1不能统计,因为Q节点要求儿子数>=3 
                    {
                        f[l][r]=add(f[l][r],mul(f[l][i],h[i+1][r]));
                        h[l][r]=add(h[l][r],mul(f[l][i],h[i+1][r]));//>=3
                    }
                }
            }
            g[l][r]=add(g[l][r],f[l][r]);//=1
        }
    }
    printf("%d\n",f[1][n]);
    return 0;
}
/*
1 2
1 2
*/
/*
2 4
1 2 3 4
1 2 4 3
*/
/*
4 8
5 3 1 2 4 6 7 8
8 7 6 5 2 1 3 4
1 2 3 4 5 6 7 8
8 7 5 6 3 2 1 4
*/
posted @ 2022-10-30 14:26  ez_lcw  阅读(50)  评论(0编辑  收藏  举报