洛谷 P1415 拆分数列 题解

Announcement

  • Programmed on 2024/3/1
  • Written on 2024/3/2

题目来源

Description

给定一个仅由数字构成的字符串 s s s,用 , 将其划分为若干个正整数,使其严格递增。

求在满足最后一个数字最小的情况下,字典序最大(第一个数最大,在此基础上第二个数最大……)的方案。

  • ∣ s ∣ ≤ 500 |s|\le500 s500

Solution

f [ i ] f[i] f[i] 表示把前 i i i 个数按照条件划分后,最后一个数字最大的起始下标为 f [ i ] f[i] f[i](此时最后一个最小)。

若将 s [ x ⋯ y ] s[x\cdots y] s[xy] 表示为 s s s 中下标属于 x x x y y y 的字符组成的数字,转移方程:
f [ i ] = max ⁡ { j ∣ j ∈ [ 1 , i ] ∩ Z , s [ f [ j − 1 ] ⋯ j − 1 ] < s [ j ⋯ i ] } f[i]=\max\{j\big|j\in[1,i]\cap\Z,s[f[j-1]\cdots j-1]<s[j\cdots i]\} f[i]=max{j j[1,i]Z,s[f[j1]j1]<s[ji]}
初始化 f [ 1 ] = 1 f[1]=1 f[1]=1,这样我们就求出了最后一个数的最小值,接着考虑字典序的限制。

g [ i ] g[i] g[i] 表示把 [ i , n ] [i,n] [i,n] 按照条件划分后,在保证最后一个数字为之前求出的最小值 s [ f [ n ] ⋯ n ] s[f[n]\cdots n] s[f[n]n] 的前提下,第一个数字最大的终止下标为 g [ i ] g[i] g[i](此时第一个数最大)。

转移方程:
g [ i ] = max ⁡ { j ∣ j ∈ [ i , ∣ s ∣ ] ∩ Z , s [ i ⋯ j ] < s [ j + 1 , g [ j + 1 ] ] } g[i]=\max\{j\big|j\in\big[i,|s|\big]\cap\Z,s[i\cdots j]<s[j+1,g[j+1]]\} g[i]=max{j j[i,s]Z,s[ij]<s[j+1,g[j+1]]}
正序求 f [ i ] f[i] f[i],再倒序求 g [ i ] g[i] g[i]

初始化 g [ f [ n ] ] = n g[f[n]]=n g[f[n]]=n,注意 f [ n ] f[n] f[n] 前面连续的一段零也需要令其 g [ i ] = n g[i]=n g[i]=n,因为前导零对最后一个数字的大小无影响,且还能使 g [ i ] g[i] g[i] 变大,从而更有可能转移。

最后输出即可。

比较两个字符串需要 O ( ∣ s ∣ ) O(|s|) O(s),枚举 f [ i ] , g [ i ] f[i],g[i] f[i],g[i] 的下标需要 O ( ∣ s ∣ ) O(|s|) O(s),转移枚举需要 O ( ∣ s ∣ ) O(|s|) O(s),总的需要 O ( ∣ s ∣ 3 ) O(|s|^3) O(s3)

Code

#include <bits/stdc++.h>
using namespace std;
int n,f[505],g[505]; // f[i]-把s[1~i]划分为递增数列,最后一个数最小为s[f[i]~i];g[i]-在最后一个数最小的情况之下,把s[i~n]划分为递增数列,第一个数最大为s[i~g[i]]
char s[505];
string get(int x,int y){
    bool Flag=0;
    string res="";
    for (int i=x;i<=y;i++){
        if (s[i]!='0') Flag=1;
        if (Flag) res+=s[i];
    }
    return res;
}
bool cmp(string x,string y){
    if (x.size()!=y.size()) return x.size()<y.size();
    return x<y;
}
int main(){
    scanf(" %s",s+1),n=strlen(s+1);
    f[1]=1;
    for (int i=2;i<=n;i++) for (int j=i;j;j--) if (cmp(get(f[j-1],j-1),get(j,i))){f[i]=j;break;}
    int t=0;
    for (int i=f[n]-1;i;i--)
        if (s[i]=='0') g[i]=n;
        else{t=i;break;}
    g[f[n]]=n;
    for (int i=t;i>0;i--) for (int j=f[n]-1;j>=i;j--) if (cmp(get(i,j),get(j+1,g[j+1]))){g[i]=j;break;}
    for (int i=1;i<=n;i=g[i]+1){
        for (int j=i;j<=g[i];j++) putchar(s[j]);
        if (g[i]!=n) putchar(',');
    }
    return 0;
}
  • 30
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值