【XSY3929】PQ 树(区间dp)

24 篇文章 0 订阅

题面

PQ 树

题解

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

在这里插入图片描述

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

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

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

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

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

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

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

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

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

不妨设 h ( i , j ) h(i,j) h(i,j) 表示仅考虑 [ i , j ] [i,j] [i,j] 中的数字,他们形成 ≥ 2 \geq 2 2 棵子树,且这些子树在 k k 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
*/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值