洛谷P1415 拆分数列(dp)

洛谷P1415 拆分数列

做两次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;
    }
}

posted on 2019-08-20 15:52  solvit  阅读(209)  评论(0编辑  收藏  举报

导航