计数(count)

【from new_dtoj 3967: 计数(count)】
题目描述
考虑一个 N个节点的二叉树,它的节点被标上了1∼N 的编号. 并且,编号为i的节点在二叉树的前序遍历中恰好是第i个出现.
我们定义Ai 表示编号为i的点在二叉树的中序遍历中出现的位置.
现在,给出M个限制条件,第i个限制条件给出了ui,vi,表示Aui<Avi ,也即中序遍历中ui在vi 之前出现.
你需要计算有多少种不同的带标号二叉树满足上述全部限制条件,答案对 1 0 9 {10^9} +7 取模.
输入
第一行一个整数T(1≤T≤5), 表示数据组数.
每组数据第一行为两个整数N,M,意义如题目所述.
接下来M 行,每行两个整数ui,vi(1≤ui,vi≤N) . 描述一条限制.
输出
对每组数据,输出一行一个整数,表示答案.
样例输入
3
5 0
3 2
1 2
2 3
3 3
1 2
2 3
3 1
样例输出
42
1
0
提示
解释
第一组数据,无任何限制时,5 个点的二叉树形态个数为42 .
第二组数据,唯一满足条件的二叉树的形态为 1 - 2 - 3.(1 为根,2, 3 均为右儿子).
第三组数据,限制条件产生矛盾.
数据范围和子任务
对于全部的测试数据,保证T≤5,N≤400,M≤ 1 0 3 {10^3} .
子任务 1(20 分):M=0 .
子任务 2(15 分):N≤10 .
子任务 3(20 分):N≤50,M≤1 .
子任务 4(15 分):N≤50 .
子任务 5(30 分):无特殊限制.
题解:
由于前序遍历是1-n,所以可以发现每一棵子树的dfs序是连续的,并且它的根是这棵子树内编号最小的点
所以设f[i][j]表示以i为一棵子树的根,这棵子树最大编号为j,能满足限制的答案值
明显若不考虑限制, f [ i ] [ j ] = k = i j F ( i + 1 , k ) F ( k + 1 , j ) f[i][j]=\sum_{k=i}^{j}F(i+1,k)*F(k+1,j)
考虑限制,我们把一个限制看成二维平面上的一个点(ui,vi),并且这个点的权值为1
那么当在顶点坐标为(k+1,i+1),(j,k);(i,i+1),(i,k);(k+1,i),(j,i)(左上和右下)内的和为0时,k是满足限制的,所以就完美解决啦
代码如下

#include <cstdio>
using namespace std;
const int P=1e9+7;
int T,n,m,f[405][405],s[405][405];
bool calm(int l1,int r1,int l2,int r2){
    if (r1<l1||r2<l2) return 1;
    return s[r1][r2]-s[l1-1][r2]-s[r1][l2-1]+s[l1-1][l2-1]==0;
}
bool judge(int l1,int r1,int l2,int r2){
    return calm(l1,r1,l2,r2)&calm(l2-1,l2-1,l2,r2)&calm(l1,r1,l2-1,l2-1);
}
int F(int l,int r){
    if (l>=r) return f[l][r]=1;
    if (~f[l][r]) return f[l][r];f[l][r]=0;
    for (int i=l;i<=r;i++) if (judge(i+1,r,l+1,i))
        f[l][r]=(1ll*f[l][r]+1ll*F(l+1,i)*F(i+1,r)%P)%P;
    return f[l][r];
}
int main(){
    for (scanf("%d",&T);T--;){
        scanf("%d%d",&n,&m);
        for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) s[i][j]=0;
        for (int x,y,i=1;i<=m;i++) scanf("%d%d",&x,&y),s[x][y]=1;
        for (int i=1;i<=n;i++) for (int j=1;j<=n;j++)
            s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1],f[i][j]=-1;
        printf("%d\n",F(1,n));
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Johnny817/article/details/83149850