[bzoj4824][洛谷P3757][Cqoi2017]老C的键盘

Description###

老 C 是个程序员。
作为一个优秀的程序员,老 C 拥有一个别具一格的键盘,据说这样可以大幅提升写程序的速度,还能让写出来的程序
在某种神奇力量的驱使之下跑得非常快。小 Q 也是一个程序员。有一天他悄悄潜入了老 C 的家中,想要看看这个
键盘究竟有何妙处。他发现,这个键盘共有n个按键,这n个按键虽然整齐的排成一列,但是每个键的高度却互不相同
。聪明的小 Q 马上将每个键的高度用 1 ~ n 的整数表示了出来,得到一个 1 ~ n 的排列 h1, h2,..., hn 。为了
回去之后可以仿造一个新键盘(新键盘每个键的高度也是一个 1 ~ n 的排列),又不要和老 C 的键盘完全一样,小 Q
决定记录下若干对按键的高度关系。作为一个程序员,小 Q 当然不会随便选几对就记下来,而是选了非常有规律的
一些按键对:对于 i =2,3, ... , n,小 Q 都记录下了一个字符<或者>,表示 h_[i/2] < h_i 或者h _[i/2] > h_i
。于是,小 Q 得到了一个长度为n ? 1的字符串,开开心心的回家了。现在,小 Q 想知道满足他所记录的高度关系的
键盘有多少个。虽然小 Q 不希望自己的键盘和老 C 的完全相同,但是完全相同也算一个满足要求的键盘。答案可
能很大,你只需要告诉小 Q 答案 mod 1,000,000,007 之后的结果即可。

Input###

输入共 1 行,包含一个正整数 n 和一个长度为 n ? 1 的只包含<和>的字符串,分别表示键
盘上按键的数量,和小 Q 记录的信息,整数和字符串之间有一个空格间隔。

Output###

输出共 1 行,包含一个整数,表示答案 mod 1,000,000,007后的结果。

Sample Input###

5 <>><

Sample Output###

3

共5个按键,第1个按键比第2个按键矮,第1个按键比第3个按键高,第2个按键比第4个

按键高,第2个按键比第5个按键矮。

这5个按键的高度排列可以是 2,4,1,3,5 , 3,4,1,2,5 , 3,4,2,1,5 。


简要题解##

根据题目描述按键高度关系连边,恰形成一棵二叉树。
树形dp+组合 求解。


想法##

看到这道题,我的第一想法是根据高度<或>连边,小的向大的连一条边,形成有向图。
题目要求的便是这个有向图拓扑排序的方案数。
然而这个东西并不太好搞……

于是,又翻题解……
考虑题中所说(h_i)(h_{i/2}) 比较,发现比较关系可以形成一棵树。
假设某个点的两个子树已各自计算出满足要求的方案数,那这两个子树在各自顺序保持不变时相互顺序怎么改变都仍满足要求。
把当前点插进来只需满足它与左右子的大小关系。
故树形dp,状态f[i][j]表示i在以i为根的子树中排第j位的方案数。
求解f[i][j]时枚举排在i前面的j-1位中有多少位在左子子树(这时要注意i与其左右子的大小关系),记下f[][]的前后缀和,带上组合数瞎搞搞就出来了……

为啥感觉这道题出的有点为树形dp而树形dp呢,感觉不太自然。


代码##

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>

#define P 1000000007

using namespace std;

typedef long long ll;
const int N = 105;

int d[N],f[N][N],sum[2][N][N];
int size[N];
int n;

int C[N][N];
void getC(){
    C[0][0]=1;
    for(int i=1;i<=n;i++){
        C[i][0]=1;
        for(int j=1;j<i;j++)
            C[i][j]=(C[i-1][j]+C[i-1][j-1])%P; /**/
        C[i][i]=1;
    }
}

int main()
{
    char ch[N];
    scanf("%d",&n);
    scanf("%s",ch);
    for(int i=2;i<=n;i++)
        d[i]=(ch[i-2]=='>');
    
    getC();
    //dp
    for(int i=n;i>0;i--){
        if(i*2>n){ //leaf
            f[i][1]=sum[0][i][1]=sum[1][i][1]=1;
            size[i]=1;
            continue;
        }
        if(i*2+1>n){ //single son
            size[i]=size[i*2]+1;
            for(int j=1;j<=size[i];j++){
                if(d[i<<1]) f[i][j]+=sum[0][i<<1][j-1];
                else f[i][j]+=sum[1][i<<1][j];
            }
            for(int j=1;j<=size[i];j++) 
                sum[0][i][j]=((ll)sum[0][i][j-1]+f[i][j])%P;
            for(int j=size[i];j>0;j--)
                sum[1][i][j]=((ll)sum[1][i][j+1]+f[i][j])%P;
            continue;
        }
        size[i]=size[i*2]+size[i*2+1]+1; //double son
        for(int j=1;j<=size[i];j++){
            for(int k1=0;k1<j && k1<=size[i<<1];k1++){
                int k2=j-1-k1,s1,s2;
                if(k2>size[i*2+1]) continue;
                if(d[i<<1]) s1=sum[0][i<<1][k1];
                else s1=sum[1][i<<1][k1+1];
                if(d[(i<<1)+1]) s2=sum[0][(i<<1)+1][k2];
                else s2=sum[1][(i<<1)+1][k2+1];
                f[i][j]=((ll)f[i][j]+(ll)((((ll)s1*s2)%P*C[j-1][k1])%P*C[size[i]-j][size[i<<1]-k1])%P)%P;
            }
        }
        for(int j=1;j<=size[i];j++) 
            sum[0][i][j]=((ll)sum[0][i][j-1]+f[i][j])%P;
        for(int j=size[i];j>0;j--)
            sum[1][i][j]=((ll)sum[1][i][j+1]+f[i][j])%P;
    }
    
    printf("%d
",sum[0][1][size[1]]);
    
    return 0;
}
既然选择了远方,便只顾风雨兼程
原文地址:https://www.cnblogs.com/lindalee/p/8447971.html