51Nod 1196 - 字串的數量(DP)
【題目描述】
【思路】
做不出來,看了討論區大佬的題解才寫出來的。
這道題是V1難度,還有V2,V3根本不會,先貼上V1的題解
下面的所有字母編號都從
開始,範圍
首先,一個合法的字串顯然是由若干個合法的“鏈”組成的。鏈的定義就是:從一個字母開始連,後面每個字母編號必須大於等於前一個的2倍,這樣儘可能的連線下去。所謂儘可能連線下去的意思是,鏈的最後一個字母編號i必須滿足
,這樣後面不能接東西了,並且只有這樣的鏈才合法。對於每個合法字串,劃分成合法鏈的方法是唯一的。
比如
的
個解 分別是
問題轉化為兩步:
(1)
表示長度為x的合法鏈的個數,求
(2)
表示長度為x的合法字串數,求
對於(2) 顯然我們有
就是長度為x的解可以先弄出一條鏈來,再構造剩餘的部分。為方便可以定義
,這樣單獨一條長度為x的合法鏈也是合法解。 其中p是最長的合法鏈的長度。
用這個式子直接推長度為
的結果複雜度是
顯然,當有n種字母的時候,
是
級別的。所以這部分複雜度是
在
比較小的時候這部分複雜度可以了。
然後上面的題解只給了 的遞推式,但在這之前還要計算出所有的 ,這個我是又用了一個 來求解的,如果設 表示長度為 的序列 ,每一項都在 中取,同時嚴格滿足 對應的序列的數量,那麼有遞推式 可以利用字首和優化,最終在 求解出 ,然後可以根據 陣列的值求出 ,然後再按照題解那樣遞推答案
#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;
}