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


    题目链接:

    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;
    }
  • 相关阅读:
    GMS问题_ThemeHostTest#testThemes
    解压system.img
    Android10_原理机制系列_AMS(ATMS)之应用的第一次启动的过程
    Android10_原理机制系列_AMS之AMS的启动
    如何用云开发高效快捷地实现短信验证码登录?
    推荐一款强大的前端CLI命令行工具
    CloudBase Framework丨第一个 Deno 部署工具是如何打造的?
    实战 | 打造一个双端自动发布的博客体系(下)
    从java注解漫谈到typescript装饰器——注解与装饰器
    TS数据类型:从C++/Java/Python到TS看元组tuple—元组的来龙去脉
  • 原文地址:https://www.cnblogs.com/yxg123123/p/7227462.html
Copyright © 2020-2023  润新知