洛谷P1415 拆分数列(dp)
做两次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;
}
}