51nod 1196 字符串的数量

用N个不同的字符(编号1 - N),组成一个字符串,有如下要求:
(1) 对于编号为i的字符,如果2 * i > n,则该字符可以作为结尾字符。如果不作为结尾字符而是中间的字符,则该字符后面可以接任意字符。
(2) 对于编号为i的字符,如果2 * i <= n,则该字符不可以作为结尾字符。作为中间字符,那么后面接的字符编号一定要 >= 2 * i。
问有多少长度为M且符合条件的字符串,由于数据很大,只需要输出该数Mod 10^9 + 7的结果。
例如:N = 2,M = 3。则abb, bab, bbb是符合条件的字符串,剩下的均为不符合条件的字符串。
Input
输入2个数,N, M中间用空格分割,N为不同字符的数量,M为字符串的长度。(2 <= N, M <= 10^6)
Output
输出符合条件的字符串的数量。由于数据很大,只需要输出该数Mod 10^9 + 7的结果。
Input示例
6 3
Output示例
73

题解:
  的确是一个比较好的dp题目,然而我只写出了n^2的暴力dp,这里还是稍微讲一下我的暴力dp吧,设dp[i][j]表示长度为i最后一个字母为j的合法字符串的个数,那么dp[1][1~n]=1,这个暴力转移是On的,写一个区间加法(差分数组),可以把转移优化到O1,。
  然而,这个是不可能再继续优化的,因为状态数就有n^2个,转移已经是O1的了,所以我们要考虑怎么转变状态。
  设f[i][j]为长度为i并以编号小于或者等于j的字母结尾的不完全链(不一定以大于n/2结尾,且除最后一个字母外,中间不可以出现>n/2的字符)的个数,满足中间不能用条件1转移。
  s[i]表示长度为i的合法链的个数。
  关于链的定义:从一个字母开始连,后面每个字母编号必须大于等于前一个的2倍,这样尽可能的连接下去。所谓尽可能连接下去的意思是,链的最后一个字母编号i必须满足2 * i > n ,这样后面不能接东西了,并且只有这样的链才合法。引自曹鹏。
  g[i]表示长度为i的合法字符串数量。
  先预处理出f数组,因为显然链的长度不会超过log2(n)所以第一维开20就好了f[i][j]=(f[i][j-1]+f[i-1][j/2])%mod;因为相当于前缀和,所以加上f[i][j-1],f[i-1][j/2]用转移2,加上一个j字母变成f[i][j]。
  那么有了f数组,那么显然s[i]=(f[i][n]-f[i][n/2])%mod;
  最后就是g数组了,因为一个合法字符串可以看成一个长度比他小的合法字符转加上一条链形成,所以g[i]=g[i]+g[i-j]*s[j];其中j为链的长度,j从1~min(log(n),i)。
代码:
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
#define ll long long
#define MAXN 1000010
const int mod=1e9+7;
using namespace std;
int f[21][MAXN],s[MAXN],g[MAXN],ans=0;
int n,m;

void pre(){
    for(int i=0;i<=n;i++) f[0][i]=1;
    for(int i=1;i<=min(m,20);i++)
        for(int j=1;j<=n;j++){
            f[i][j]=((ll)f[i][j-1]+(ll)f[i-1][j/2])%mod;
        }
    for(int i=1;i<=min(m,20);i++) s[i]=((ll)f[i][n]-(ll)f[i][n/2]+mod)%mod;
}

int main()
{
    scanf("%d%d",&n,&m);
    pre();
    g[0]=1;
    for(int i=1;i<=m;i++)
        for(int j=1;j<=min(20,i);j++){
            g[i]=((ll)g[i]+(ll)g[i-j]*s[j])%mod;
        }
    printf("%d
",g[m]%mod);
    return 0;
}
原文地址:https://www.cnblogs.com/renjianshige/p/7535754.html