1. 程式人生 > >51Nod 1196 - 字串的數量(DP)

51Nod 1196 - 字串的數量(DP)

【題目描述】
在這裡插入圖片描述【思路】
做不出來,看了討論區大佬的題解才寫出來的。
這道題是V1難度,還有V2,V3根本不會,先貼上V1的題解
下面的所有字母編號都從 1 1 開始,範圍 [ 1 , n

] [1,n]

首先,一個合法的字串顯然是由若干個合法的“鏈”組成的。鏈的定義就是:從一個字母開始連,後面每個字母編號必須大於等於前一個的2倍,這樣儘可能的連線下去。所謂儘可能連線下去的意思是,鏈的最後一個字母編號i必須滿足 2 i

> n 2 * i > n ,這樣後面不能接東西了,並且只有這樣的鏈才合法。對於每個合法字串,劃分成合法鏈的方法是唯一的。
比如 N = 2 M
= 3 N = 2 M = 3
3 3 個解 分別是 ( a b ) ( b ) ( b ) ( a b ) ( b ) ( b ) ( b ) (ab)(b), (b)(ab), (b)(b)(b)
問題轉化為兩步:
(1) g ( x ) g(x) 表示長度為x的合法鏈的個數,求 g ( x ) g(x)
(2) v ( x ) v(x) 表示長度為x的合法字串數,求 v ( x ) v(x)
對於(2) 顯然我們有 v ( x ) = g ( 1 ) v ( x 1 ) + g ( 2 ) v ( x 2 ) + . . . + g ( p ) v ( x p ) v(x) = g(1) * v(x - 1) + g(2) * v(x - 2) +...+g(p) * v(x - p)
就是長度為x的解可以先弄出一條鏈來,再構造剩餘的部分。為方便可以定義 v ( 0 ) = 1 v(0) = 1 ,這樣單獨一條長度為x的合法鏈也是合法解。 其中p是最長的合法鏈的長度。
用這個式子直接推長度為 m m 的結果複雜度是 O ( m p ) O(m * p)
顯然,當有n種字母的時候, p p O ( l o g n ) O(logn) 級別的。所以這部分複雜度是 O ( m l o g n ) O(m*logn) m , n m,n 比較小的時候這部分複雜度可以了。

然後上面的題解只給了 v ( x ) v(x) 的遞推式,但在這之前還要計算出所有的 g ( x ) g(x) ,這個我是又用了一個 d p dp 來求解的,如果設 d p ( i , j ) dp(i,j) 表示長度為 i i 的序列 ,每一項都在 [ 1 , n ] [1,n] 中取,同時嚴格滿足 a x 2 < = a x + 1 a_x*2<=a_{x+1} 對應的序列的數量,那麼有遞推式 d p ( i , j ) = k = 1 j / 2 d p ( i 1 , k )      ( i < = l o g n ) dp(i,j)=\sum_{k=1}^{\lfloor j/2 \rfloor}dp(i-1,k) \ \ \ \ (i<=logn) 可以利用字首和優化,最終在 O ( n l o g n ) O(n*logn) 求解出 d p dp ,然後可以根據 d p dp 陣列的值求出 g g ,然後再按照題解那樣遞推答案

#include<bits/stdc++.h>
#define min(a,b)(a<b?a:b)
using namespace std;

const int maxn=1000005;
const int mod=1e9+7;

int n,m,logn=1;
int dp[30][maxn],g[30];
int ans[maxn];

int main(){
    scanf("%d%d",&n,&m);
    while((1<<(logn))<=n) ++logn;
    for(int j=1;j<=n;++j) dp[1][j]=dp[1][j-1]+1;
    for(int i=2;i<=logn;++i){
        for(int j=(1<<(i-1));j<=n;++j){
            dp[i][j]=dp[i-1][j/2];//先求出長度為i,以j為末尾的序列數量
        }
        for(int j=(1<<(i-1));j<=n;++j){
            dp[i][j]=(dp[i][j]+dp[i][j-1])%mod;//最後再算一下字首和
        }
    }
    for(int i=1;i<=logn;++i) g[i]=((dp[i][n]-dp[i][n/2])%mod+mod)%mod;

    ans[0]=1;
    ans[1]=n-n/2;
    for(int i=2;i<=m;++i){
        for(int j=1;j<=min(i,logn);++j){
            ans[i]=(ans[i]+(long long)ans[i-j]*g[j]%mod)%mod;
        }
    }
    printf("%d\n",ans[m]);
    return 0;
}