做两次dp求解。
记 (T(i,j)) 表示从原数列下标 (i) 取到 (j) 的数字组成的数。
(d[i]) 表示前 (i) 个数字分成任意多个递增数且最后的数最小时,最后的数为 (T(d[i],i)) 。初始化 (d[i] = 1) ,转移方程式为 (d[i] = max {j | T(d[j-1],j-1) < T(j,i) }) 。
(f[i]) 表示从 (i) 开始的序列第一个数最大的终止下标 (f[i]) ,转移方程式为 (f[i]=max {j | T(i,j)<T(j+1,f[j+1]) }) 。
注意最后一个数有前导零时存在细节问题,如数据 (1234050) 应划分为 (12,34,050) 。
解决方法是把最后一个数前面的前导 (0) 的 (f) 值都指向 (n) 。
打印时从 (1) 开始沿 (f) 打印答案。
时间复杂度为 (O(n^3)) ,由于数据大部分为随机,实际运行效率接近 (O(n^2)) 。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
const int N=505,INF=2e9+5;
char s[N];
int n,a[N];
bool small(int l1,int r1,int l2,int r2){
while(l1<=r1&&a[l1]==0) l1++;
while(l2<=r2&&a[l2]==0) l2++;
if(r1-l1+1==0||r2-l2+1==0) return false;
if(r1-l1+1<r2-l2+1) return true;
if(r1-l1+1>r2-l2+1) return false;
int len=r1-l1+1;
for(int i=0;i<len;i++){
if(a[l1+i]<a[l2+i]) return true;
if(a[l1+i]>a[l2+i]) return false;
}
return false;
}
int d[N];
void dp1(){
for(int i=1;i<=n;i++){
d[i]=1;
for(int j=i;j>=1;j--)
if(small(d[j-1],j-1,j,i)) {d[i]=j;break;}
}
}
int f[N];
void dp2(){
f[d[n]]=n;int zero=d[n];
/******/
while(a[zero-1]==0) f[zero-1]=n,zero--;
for(int i=d[n]-1;i>=1;i--){
for(int j=d[n]-1;j>=i;j--)
if(small(i,j,j+1,f[j+1])) {f[i]=j;break;}
}
}
int main(){
scanf("%s",s+1);
n=strlen(s+1);
for(int i=1;i<=n;i++) a[i]=s[i]-'0';
dp1();
dp2();
int pos=1,flag=0;
while(pos<=n){
if(flag) putchar(',');
flag=1;
for(int i=pos;i<=f[pos];i++) printf("%d",a[i]);
pos=f[pos]+1;
}
}