数字分割——题解

7 篇文章 0 订阅
6 篇文章 0 订阅

题目大意

如题,求将一个 N N 位数字分割开的方案数,要求割出的数字从左往右严格递增,且数字不能有前导0
N<=5000

很容易写一个 O(N3) O ( N 3 ) 的DP:
F[i][j] F [ i ] [ j ] 表示前 i i 位,其中最后一个数字位数为j的方案数
F[i][j]=(j1k=1F[ij][k])+F[ij][j]|[ij+1,i][i2j+1,ij] F [ i ] [ j ] = ( ∑ k = 1 j − 1 F [ i − j ] [ k ] ) + F [ i − j ] [ j ] | 当 前 数 字 [ i − j + 1 , i ] 大 于 上 一 数 字 [ i − 2 ∗ j + 1 , i − j ]

j1k=1F[ij][k] ∑ k = 1 j − 1 F [ i − j ] [ k ] 这一部分一个前缀和优化就 O(1) O ( 1 )

关键是如何快速比较当前数字 [ij+1,i] [ i − j + 1 , i ] 与上一数字 [i2j+1,ij] [ i − 2 ∗ j + 1 , i − j ] 的大小关系
暴力比较最坏显然是 O(N) O ( N ) 级别的(PS:虽然最后竟有90分!!!

上高端数据结构?

“可以用字符串哈希+二分查找第一个不相等字符的位置,来确定这两段的大小,复杂度 O(log) O ( l o g ) ”——摘自Solution

实际上我写的不需那么高(ma)端(fan)。。
考虑暴力比较,每次都从字符串头开始比较,到不同处出结果:
暴力比较
记上次不同的位置位为 lst l s t ,则当 i i 后推一个时,[i,k] [i+L+1,lst1] [ i + L + 1 , l s t − 1 ] 显然是相同的,就无需比较了
优化

for(int L=1,tL=n>>1;L<=tL;L++)
   for(int i=1,ti=n-(L<<1)+1,lst=0;i<=ti;i++){
      for(++lst;lst<i+L;lst++) if(s[lst]!=s[lst+L]){if(s[lst]<s[lst+L]) cmp[i][L]=1;break;}
      lst-=(lst!=i);//回退有细节 
   }

这样就基本保证了 lst l s t 指针的不降(最后要回退一格),均摊复杂度为 O(N) O ( N ) O(N2) O ( N 2 ) 预处理出了字符串比较
DP复杂度也优至 O(N2) O ( N 2 ) ,总复杂度就为 O(N2) O ( N 2 ) (实际还小得多^_^)
第三题代码也如此简洁——DP没有F数组

#pragma GCC optimize(3)
#include<cstdio>
using namespace std;
const int maxn=5005,TT=(1e9)+7;
int n,g[maxn][maxn];char s[maxn];bool cmp[maxn][maxn];
int main(){
    scanf("%d%s",&n,s+1);
    for(int L=1,tL=n>>1;L<=tL;L++)
      for(int i=1,ti=n-(L<<1)+1,lst=0;i<=ti;i++){
        for(++lst;lst<i+L;lst++) if(s[lst]!=s[lst+L]){if(s[lst]<s[lst+L]) cmp[i][L]=1;break;}
        lst-=(lst!=i);//回退有细节 
      }
    for(int i=1;i<=n;i++){
      for(int j=1;j<i;j++){
        g[i][j]=g[i][j-1];
        if(s[i-j+1]!='0') 
          if(i>=(j<<1)&&cmp[i-(j<<1)+1][j]){if((g[i][j]+=g[i-j][j])>=TT) g[i][j]-=TT;}
            else if((g[i][j]+=g[i-j][(j-1<i-j?j-1:i-j)])>=TT) g[i][j]-=TT;
      }
      if((g[i][i]=g[i][i-1]+1)>=TT) g[i][i]-=TT;
    }
    printf("%d\n",g[n][n]);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值