DP - CF 611D New Year and Ancient Prophecy

题目:

New Year and Ancient Prophecy

题意:

将一个纯数字串划分成若干段,要求各段代表的数值严格递增,不允许出现前导零

思路:

一开始将状态划分成[i,j,k],代表[i,j]区间段,结尾段长度为k,发现写不出转移方程,而且空间时间复杂度都不太科学
重新考虑状态,dp[i,j]代表区间段[0,i],结尾段长度为j+1(!注意,这里不是j,当然处理成j更直观), 这样可以列出状态转移方程

dp[i,j]=k=0j1dp[ij1,k]+T

T={dp[ij1,j]0,str[ij1j,ij1]<str[ij,j],str[ij1j,ij1]str[ij,j]

显然,分情况进行讨论

  • 对于任何的 dp[i-j-1,k],str[0,i-j-1]的结尾段一定比str[0,i]的结尾段小,因为k小于j,则将str[i-j, j]段并入之前的任意一种分法均可,无需判断直接状态叠加
  • 对于 k==j 时的状态数 T ,需要判断str[i-j-1-j, i-j-1]段A, 与str[i-j,j]段B之间的大小关系,若A小于B同上一种情况,反之这种情况不能合并,状态数为0
  • 对于 k大于j ,无法合并,因为新的结尾段肯定比之前小,不满足题意

至此,列出状态转移方程,但时间复杂度为O(n3),状态数 n2 ,两种情况的转移均为 O(n) ,显然,要么重新划分状态,要么对状态转移进行优化,这里选择后者

  • 对于第一种情况, sum[i,j]=jk=0dp[i,k]
  • 对于第二种,先对整个串求LCP(最长公共前缀),即可 O(1) 进行比较

代码:

#include <bits/stdc++.h>
#define maxn 5005
const int modn=1e9+7;
using namespace std;

char str[maxn];
long long dp[maxn][maxn]={0};
long long sum[maxn][maxn]={0};
int lcp[maxn][maxn]={0};
int n;
void getLcp(){
    for(int i=n-1;i>-1;--i)
        for (int j=n-1;j>-1;--j)
            if (str[i] == str[j])
                lcp[i][j] = lcp[i+1][j+1] + 1;
            else lcp[i][j] = 0;
}

int main(){
    scanf("%d",&n);
    scanf("%s",str);
    getLcp();
    for (int i=0;i<n;++i){
        dp[i][i] = 1;
        sum[i][i] = 1;
    }
    for (int i=1;i<n;++i){
        for (int j=0;j<i;++j){
            if (j!=0) sum[i][j] = (sum[i][j] + sum[i][j-1]) % modn;
            if (str[i-j] == '0') continue;
            if (j!=0)
                dp[i][j] = (dp[i][j] + sum[i-j-1][min(j-1,i-j-1)])%modn;
            if (i-j-1-j >= 0){
                int tlcp = lcp[i-j-1-j][i-j];
                if (tlcp-1<j && str[i-j-1-j+tlcp] < str[i-j+tlcp])
                    dp[i][j] = (dp[i][j] + dp[i-j-1][j]) % modn;
            }
            sum[i][j] = (sum[i][j] + dp[i][j]) % modn;
        }
        sum[i][i] = (sum[i][i] + sum[i][i-1]) % modn;
    }
    cout<<sum[n-1][n-1]<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值