PQ树
- 对于非叶子节点,它只能调换它的儿子的顺序,却不能调换整颗子树的顺序
- 合法的PQ树都能表示出1,2,3,…,n 这个排列
- 由以上两点可以得出:PQ树的每棵子树对应的一定都是一段连续的数字区间
- 考虑区间DP,设f(l,r)f(l,r)f(l,r)表示数字l~r构成一棵PQ树的方案数
- 数字l~r 能够构成一棵子树 当且仅当 数字l~r 在题目给出的所有排列中都在一段连续区间中,我们定义这样的 [l,r] 是“好的”
- 若[l,r]不是"好的",f(l,r)=0f(l,r)=0f(l,r)=0
- 若[l,r]是"好的",要算f(l,r)f(l,r)f(l,r),分 树根为P点 和 树根为Q点 两种情况讨论
- 若树根为P点:f(l,r)+=∑i=lr−1f(l,i)g(i+1,r)f(l,r)+=\sum_{i=l}^{r-1}f(l,i)g(i+1,r)f(l,r)+=∑i=lr−1f(l,i)g(i+1,r)(其中g(i+1,r)g(i+1,r)g(i+1,r)表示数字i+1~r构成一片PQ树森林(可以只有一颗树) 的方案数)
- g(l,r)=f(l,r)+∑i=lr−1f(l,i)g(i+1,r)g(l,r)=f(l,r)+\sum_{i=l}^{r-1}f(l,i)g(i+1,r)g(l,r)=f(l,r)+∑i=lr−1f(l,i)g(i+1,r)(注意这里并不要求[l,r]是“好的”)
- 若树根为Q点:
f(l,r)+=∑i=lr−1[在题目给出的排列中,数字i,i+1,r出现的位置均满足单调递增或递减]f(l,i)q(i+1,r)f(l,r)+=\sum_{i=l}^{r-1}[在题目给出的排列中,数字i,i+1,r出现的位置均满足单调递增或递减]f(l,i)q(i+1,r)f(l,r)+=∑i=lr−1[在题目给出的排列中,数字i,i+1,r出现的位置均满足单调递增或递减]f(l,i)q(i+1,r)
(q(i+1,r)q(i+1,r)q(i+1,r)表示数字i+1~r构成Q节点下除第一棵子树外的其它子树(至少2棵) 的方案数)
ps:因为数字i,i+1,r一定分属3棵子树,所以它们的位置关系可以代表其所在子树的位置关系
- q(l,r)=∑i=lr−1f(l,i)(f(i+1,r)+[在题目给出的排列中,数字i,i+1,r出现的位置均满足单调递增或递减]q(i+1,r))q(l,r)=\sum_{i=l}^{r-1}f(l,i)(f(i+1,r)+[在题目给出的排列中,数字i,i+1,r出现的位置均满足单调递增或递减]q(i+1,r))q(l,r)=∑i=lr−1f(l,i)(f(i+1,r)+[在题目给出的排列中,数字i,i+1,r出现的位置均满足单调递增或递减]q(i+1,r))
ps:这里在实现上有一个小技巧:与其在dp时不断判断 数字i,i+1,r出现的位置是否满足单调递增或递减 ,我们在求q时就把这个条件加进去,即若 某排列中数字l-1l,l,r出现的位置不满足单调递增或递减,直接令q(l,r)=0q(l,r)=0q(l,r)=0
pps:关于转移式如何推出——其实我们计算f,g,qf,g,qf,g,q的基本方法都是枚举第一棵子树,然后讨论剩余子树的情况
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=510;
const ll mod=1e9+7;
int K,n,a[N][N],pos[N][N],num[N][N];
bool ok[N][N];
ll f[N][N],g[N][N],q[N][N];
//f[l][r]:数l~r构成一棵PQ树的方案数
//g[l][r]:数l~r构成一片PQ树森林的方案数(可以只有一棵树)
//q[l][r]:数l~r构成Q节点下除第一棵子树外的其它子树 的方案数
void pre(){for(int i=1;i<=K;i++){for(int j=1;j<=n;j++){int minn=a[i][j],maxn=a[i][j];for(int k=j;k<=n;k++){minn=min(minn,a[i][k]);maxn=max(maxn,a[i][k]);if(k-j==maxn-minn){//排列i中区间[j,k]里的数是连续的num[minn][maxn]++;//即区间[j,k]里的数在排列i中属于一段连续区间 } }}}for(int i=1;i<=n;i++)for(int j=i;j<=n;j++)ok[i][j]=(num[i][j]==K);
}
ll F(int l,int r);
ll G(int l,int r);
ll Q(int l,int r);
ll F(int l,int r){if(!ok[l][r]) return 0;if(l==r) return 1;if(f[l][r]!=-1) return f[l][r];f[l][r]=0;for(int i=l;i<r;i++){f[l][r]=(f[l][r]+F(l,i)*G(i+1,r)%mod)%mod;//树根是P节点 f[l][r]=(f[l][r]+F(l,i)*Q(i+1,r)%mod)%mod;//树根是Q节点 }return f[l][r];
}
ll G(int l,int r){if(l>r) return 0;if(l==r) return 1;if(g[l][r]!=-1) return g[l][r];g[l][r]=F(l,r);for(int i=l;i<r;i++){g[l][r]=(g[l][r]+F(l,i)*G(i+1,r)%mod)%mod;}return g[l][r];
}
ll Q(int l,int r){if(l>=r) return 0;if(q[l][r]!=-1) return q[l][r];for(int i=1;i<=K;i++){if((pos[i][l-1]<pos[i][l])^(pos[i][l]<pos[i][r])){q[l][r]=0;return 0;}}q[l][r]=0;for(int i=l;i<r;i++){q[l][r]=(q[l][r]+F(l,i)*((Q(i+1,r)+F(i+1,r))%mod)%mod)%mod;}return q[l][r];
}
int main(){scanf("%d%d",&K,&n);for(int i=1;i<=K;i++){for(int j=1;j<=n;j++){scanf("%d",&a[i][j]);pos[i][a[i][j]]=j;}}pre();memset(f,-1,sizeof(f));memset(g,-1,sizeof(g));memset(q,-1,sizeof(q));printf("%d\n",F(1,n));return 0;
}