数字分割——题解

题目大意

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

很容易写一个 O ( N 3 ) 的DP:
F [ i ] [ j ] 表示前 i 位,其中最后一个数字位数为 j 的方案数
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 ]

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

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

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

实际上我写的不需那么高(ma)端(fan)。。
考虑暴力比较,每次都从字符串头开始比较,到不同处出结果:
暴力比较
记上次不同的位置位为 l s t ,则当 i 后推一个时, [ i , k ] [ 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);//回退有细节 
   }

这样就基本保证了 l s t 指针的不降(最后要回退一格),均摊复杂度为 O ( N ) O ( N 2 ) 预处理出了字符串比较
DP复杂度也优至 O ( N 2 ) ,总复杂度就为 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;
}

猜你喜欢

转载自blog.csdn.net/qq_42403731/article/details/81808896