1. 程式人生 > >51nod 1196 字符串的數量

51nod 1196 字符串的數量

出了 要求 namespace int tput for div space amp

用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\n",g[m]%mod); return 0; }

51nod 1196 字符串的數量