1. 程式人生 > >Codeforces 611D.New Year and Ancient Prophecy (dp + lcp)

Codeforces 611D.New Year and Ancient Prophecy (dp + lcp)

ack com names getchar turn 一個數 長度 getc targe

題目鏈接:

http://codeforces.com/problemset/problem/611/D

題意:

長為n的只有數字組成的字符串(n<=5000),問能分割成多少組數字,這些數字裏不含前導0,且數字的大小滿足嚴格單調遞增

思路:

from: http://blog.csdn.net/qwb492859377/article/details/50445450 qwb orz

最難的地方,就是如何去快速判斷兩個數字的大小誰大誰小呢?

我們先來講下最長公共前綴lcp的定義。如果有串A和串B,lcp[i][j]表示的是串A從原串第i位置開始,串B從原串第j位置開始,那麽從這兩個位置開始的有多少個字符相等

那麽如何來求lcp呢,方程很簡單,看了都能懂

lcp[i][j]=lcp[i+1][j+1]+1,if(s[i]==s[j])

lcp[i][j]=0,if(s[i]!=s[j])

求出lcp後如何快速判斷兩個區間的大數字是否相等呢?

假如lcp[a][b]>=len ,說明兩個區間的數字是完全相等的,此時肯定數字是相等的

如果lcp[a][b]<len,那麽我們只需要比較s[a+lcp[a][b]]和s[b+lcp[a][b]]的大小就可以了,因為這個位置是兩個子串第一個不一樣的位置.

知道了這個的話,我們就能再來考慮這道題的dp了。

設dp[i][j](j<=i)表示現在只考慮前i個,最後一個數字是以第j個開頭。

那麽就能得到轉移方程

dp[i][j]+=dp[j-1][k], max(j+1-len,1)<=k<=j-1

dp[i][j]+=dp[j-1][j-len],如果j-len>=1且以j-len開頭比以j開頭且長度為len數字要小

邊界條件是j=1,此時應該等於1

感覺還有地方,,就是寫dp的時候總是處理不好邊界條件

其實感覺如果就在第二層for裏面寫if判斷邊界,這是一種非常好的方法,減少了很多思考,。

收獲:

考慮如何轉移,dp[i][j]可以從前j-1個並且長度小於當前長度的位置轉移過來,之後再深入的考慮長度相等的時候要不要轉移,就要判斷兩個字符串大小了,要用O(1)優秀的做法, 就是lcp。 容易想到n^3的dp【思考許久,其實我一點都沒有思路】, dp[i][j] += dp[j-1][k], 學到了使用前綴和優化成n^2, pre[i][j]就是考慮到第i個位置,數字以第1~j開頭的所有方法數。

代碼:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define MS(a) memset(a,0,sizeof(a))
#define MP make_pair
#define PB push_back
const int INF = 0x3f3f3f3f;
const ll INFLL = 0x3f3f3f3f3f3f3f3fLL;
inline ll read(){
    ll x=0,f=1;char ch=getchar();
    while(ch<0||ch>9){if(ch==-)f=-1;ch=getchar();}
    while(ch>=0&&ch<=9){x=x*10+ch-0;ch=getchar();}
    return x*f;
}
//////////////////////////////////////////////////////////////////////////
const int maxn = 5e3+10;
const int mod = 1e9+7;

int n;
char s[maxn];
int dp[maxn][maxn],lcp[maxn][maxn],pre[maxn][maxn];

bool check(int a,int b,int len){
    int t = lcp[a][b];
    if(t<len && s[a+t]<s[b+t]) return true;
    return false;
}

int main(){
    cin >> n >> s+1;

    for(int i=n; i>=1; i--)
        for(int j=n; j>=1; j--){
            if(s[i]==s[j]) lcp[i][j] = lcp[i+1][j+1]+1;
            else lcp[i][j] = 0;
        }

    for(int i=1; i<=n; i++){
        for(int j=1; j<=i; j++){
            if(s[j]==0) continue;
            if(j==1) dp[i][j]=1;
            int len = i-j+1;
            int l = max(j-len+1,1), r = j-1;
            if(l<=r) dp[i][j] = (dp[i][j]+pre[j-1][r]-pre[j-1][l-1]+mod)%mod;
            if(j-len>=1 && check(j-len,j,len)) dp[i][j] = (dp[i][j]+dp[j-1][j-len])%mod;
        }
        for(int j=1; j<=i; j++){
            pre[i][j] = (pre[i][j-1]+dp[i][j])%mod;
        }
    }

    int ans = 0;
    for(int i=1; i<=n; i++){
        ans = (ans+dp[n][i])%mod;
    }
    cout << ans << endl;


    return 0;
}

Codeforces 611D.New Year and Ancient Prophecy (dp + lcp)