codeforces-611D-New Year and Ancient Prophecy【lcp+dp】【好题】

611D-New Year and Ancient Prophecy

                    time limit per test2.5 seconds  memory limit per test512 megabytes

Limak is a little polar bear. In the snow he found a scroll with the ancient prophecy. Limak doesn’t know any ancient languages and thus is unable to understand the prophecy. But he knows digits!

One fragment of the prophecy is a sequence of n digits. The first digit isn’t zero. Limak thinks that it’s a list of some special years. It’s hard to see any commas or spaces, so maybe ancient people didn’t use them. Now Limak wonders what years are listed there.

Limak assumes three things:

Years are listed in the strictly increasing order;
Every year is a positive integer number;
There are no leading zeros.
Limak is going to consider all possible ways to split a sequence into numbers (years), satisfying the conditions above. He will do it without any help. However, he asked you to tell him the number of ways to do so. Since this number may be very large, you are only asked to calculate it modulo 109 + 7.

Input
The first line of the input contains a single integer n (1 ≤ n ≤ 5000) — the number of digits.

The second line contains a string of digits and has length equal to n. It’s guaranteed that the first digit is not ‘0’.

Output
Print the number of ways to correctly split the given sequence modulo 109 + 7.

input
6
123434
output
8

input
8
20152016
output
4

Note
In the first sample there are 8 ways to split the sequence:

“123434” = “123434” (maybe the given sequence is just one big number)
“123434” = “1” + “23434”
“123434” = “12” + “3434”
“123434” = “123” + “434”
“123434” = “1” + “23” + “434”
“123434” = “1” + “2” + “3434”
“123434” = “1” + “2” + “3” + “434”
“123434” = “1” + “2” + “3” + “4” + “34”
Note that we don’t count a split “123434” = “12” + “34” + “34” because numbers have to be strictly increasing.

In the second sample there are 4 ways:

“20152016” = “20152016”
“20152016” = “20” + “152016”
“20152016” = “201” + “52016”
“20152016” = “2015” + “2016”

题目链接:cf-611D

题目大意:给一串字符串(只含有数字),你可以将他进行拆分,问,有多少种拆分方式使得,拆分后的数字需要严格递增

题目思路:这道题我觉得挺难的,看了好久题解orz

① 首先,解决如何判断拆分出来的两个数字大小的问题,用到最长公共前缀和,如果有串A和串B,lcp[i][j]表示的是串A从原串第i位置开始,串B从原串第j位置开始,那么从这两个位置开始的有多少个字符相等

void getLcp() //最长公共前缀lcp
{
    for (int i = n; i > 0; i--)
    {
        for (int j = n; j > i; j--)
        {
            if (s[i] == s[j]) lcp[i][j] = lcp[i + 1][j + 1] + 1;
            else lcp[i][j] = 0;
        }
    }
} 

知道了最长公共前缀,然后我们来比较两个字符串分别在原串上以 i,j 为起点,len为 j 的字符串长度

如果,lcp[i][j] >= len,说明这两个数字相等,则不满足条件

如果,lcp[i][j] < len,接下来比较s[i + lcp[i][j]] 和 s[j + lcp[i][j]] 即比较第一个不同的位置的大小

所以,满足条件的串需满足,lcp[i][j] < len && s[i + lcp[i][j]] < s[j + lcp[i][j]];

② dp[i][j] 表示 前 i 长度,最后一个数字长度为 j 的方法数

codeforces-611D-New Year and Ancient Prophecy【lcp+dp】【好题】_第1张图片

  1. 处理前缀0,如果这个数字是0,那么continue;

  2. 我们可以确定的是,如果j的长度比前面一个数字长,那么就一定满足条件(一定比前面一个数字大)。所以dp[i][j] += sum[len - 1][j - 1] //sum[len - 1][j - 1] 表示原串前len - 1的长度,最后一个数字长度小于等于j - 1的总个数。

  3. 接下来处理,j 的长度和前面一个数字相等的情况。首先排除如果前面总长度都没有j长的情况,那么肯定不存在。如果存在,那么比较前面一个数字和 j 的大小,利用前缀和(前面已经解释过)果满足条件, dp[i][j] = (dp[i][j] + dp[len - 1][j]) % mod;

  4. 最后别忘了处理sum

void solve()
{
    for (int i = 0; i <= n; i++) sum[0][i] = 1;   //初始化,sum[0]
    for (int i = 1 ; i <= n; i++)
    {
        for (int j = 1; j <= i; j++)
        {
            int len = i - j + 1;  //j串的开头位置 
            if (s[len] == '0') continue;   //处理前缀0

            /*1.处理前面最后一段比j短的情况(即数字一定比j小)*/ 
            dp[i][j] = sum[len - 1][j - 1]; //sum[i][j]表示前i长度,最后一个长度为小于j的方法总数

            /*2.处理前面最后一段和j相等的情况*/
            int tmp = len - j;
            if (tmp < 1) continue;  //前面总长度没有j长
            if (check(tmp,len) && lcp[tmp][len] < j)  //判断j这段是否满足条件 
            {
                dp[i][j] = (dp[i][j] + dp[len - 1][j]) % mod;
            } 
        }
        for (int j = 1; j <= n; j++)
        {
            sum[i][j] = (sum[i][j - 1] + dp[i][j]) % mod; 
        }
    } 
}

以下是代码:

#include <bits/stdc++.h>
#define mst(a) memset(a,0,sizeof (a))
#define FOR(i,n) for (int i = 0; i < n; i++)
#define INF 1e9
#define mod 1000000007
#define eps 1e-10
using namespace std;

typedef long long ll;
int n;
string s;
int lcp[5005][5005];
int dp[5005][5005];  //前i长度,最后一个数长度为j的方法数 
int sum[5005][5005];
void getLcp() //最长公共前缀lcp
{
    for (int i = n; i > 0; i--)
    {
        for (int j = n; j > i; j--)
        {
            if (s[i] == s[j]) lcp[i][j] = lcp[i + 1][j + 1] + 1;
            else lcp[i][j] = 0;
        }
    }
} 
bool check(int a,int b)
{
    char ret1 = s[a+lcp[a][b]];
    char ret2 = s[b+lcp[a][b]];
    if (ret1 < ret2) return true;  //满足条件返回1 
    else return false;
}
void solve()
{
    for (int i = 0; i <= n; i++) sum[0][i] = 1;   //初始化
    for (int i = 1 ; i <= n; i++)
    {
        for (int j = 1; j <= i; j++)
        {
            /*在前面基础上加上一段j长度*/ 
            int len = i - j + 1;  //j串的开头位置 
            if (s[len] == '0') continue;   //处理前缀0

            /*1.处理前面最后一段比j短的情况(即数字一定比j小)*/ 
            dp[i][j] = sum[len - 1][j - 1]; //sum[i][j]表示前i长度,最后一个长度为小于j的方法总数

            /*2.处理前面最后一段和j相等的情况*/
            int tmp = len - j;
            if (tmp < 1) continue;  //前面总长度没有j长
            if (check(tmp,len) && lcp[tmp][len] < j)  //判断j这段是否满足条件 
            {
                dp[i][j] = (dp[i][j] + dp[len - 1][j]) % mod;
            } 
        }
        for (int j = 1; j <= n; j++)
        {
            sum[i][j] = (sum[i][j - 1] + dp[i][j]) % mod; 
        }
    } 
}
int main(){ 
    cin >> n >> s;
    s = " " + s;
    /*求出最长公共前缀lcp,用于快速比较两个数的大小 如果lcp[a][b] >= len,说明两个数字相等 否则比较s[a+lcp[a][b]]和s[b+lcp[a][b]]即可 */ 
    getLcp();   //最长公共前缀lcp
    solve();
    ll ans = 0; 
    for (int i = 1; i <= n; i++)
    {
        ans = (ans + dp[n][i]) % mod;
    }
    cout << ans << endl;
    return 0;
}

你可能感兴趣的:(dp,codeforces,lcp,611D)