题意
给一串长度不超过80个长度的数字串,在数字串中加入若干个逗号,使得这些数字变成一个严格递增的子序列,并且要求最后一个数尽可能的小,第一个数尽可能的大,注意前导的0。
思路
两次dp,dp这个东西真的玄学,根本想不到怎么下手切入这道题,反正我是没想出来,是我太菜了,参考了网上大神的一些博客才弄懂大致的思路做法。
- 先写好判断两个区间哪个数字串大小的函数,要去除前导的。
//[x1,y1]是前面一个数;[x2,y2]是后面一个数,严格递增那么后面的数必须大于前面的数。
bool check(int x1,int y1,int x2,int y2)
{
while(s[x1] == '0' && x1 <= y1) x1++; //去除前导0
while(s[x2] == '0' && x2 <= y2) x2++; //同上
if(x1 > y1) return true; //前面的数全是0
if(x2 > y2) return false; //后面的数全是0
if(y1 - x1 > y2 - x2) return false; //长度大小,去除前导0的情况下长度越大数越大
if(y1 - x1 < y2 - x2) return true;
while(x1 <= y1){ //长度相等逐个判断
if(s[x1] > s[x2]) return false;
if(s[x1] < s[x2]) return true;
x1++;x2++;
}
return false; //两个数相等
}
- 第一次dp从前往后dp,并且夹带着一些小贪心的思想,每个数长度尽可能的短,这样每次增加一个长度,一旦满足当前这个数大于前面那个break。因为越短的话后面数字越有可能分成若干份,这样下去就能使得最后一个数小。
dp[i]的定义为,从i开始往前dp[i]个长度,也就是i - dp[i] + 1是这个数的左端点,i是右端点。字符串从1开始输入,方便处理。
dp[1] = 1;
for(int i = 2;i <= n;i++){ //n代表字符串长度
dp[i] = i;
for(int j = i-1;j >= 1;j--){
if(check(j-dp[j]+1,j,j+1,i)){ //一旦前面区间的数字串小于后面区间的数字串就break。
dp[i] = i - j; //保证每个数字串长度尽量小,数值也就小了。
break;
}
}
}
- 第二次dp从后往前,先把最后一个数定下来,然后从最后一个数的左端点开始往前找,也是一样找到就break。注意的细节就是0,这时候遇到的0只能算在后面一个数上,不能算在当前这个数中,原因很简单,现在遇到的0在当前这个数中是被视为后导0相当于一个0放大10倍,放到后面的数中是前导0,而前导0就相当于没有,也就是说遇到0就更新后一个数的左端点,让左端点前移~
int lst = n - dp[n] + 1;
dp[lst] = dp[n];
for(int i = lst - 1;i >= 1;i--){
if(s[i] == '0'){ //遇到0就更新后
dp[i] = dp[i+1] + 1; //更新后面一个数的长度
continue;
}
for(int j = lst;j >= 1;j--){ //倒过来找,从最后一个数出发
if(check(i,j-1,j,j+dp[j]-1)){ //[i,j-1]是当前这个数,[j,j+dp[j]-1]是后面一个数
dp[i] = j - i; //找到就算出长度
break;
}
}
}
手动推导了很久发现真的很奇妙,但是我太菜根本想不到这么深的层次。完整版代码。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <string>
#include <algorithm>
#include <cmath>
int dp[100];
char s[100];
bool check(int x1,int y1,int x2,int y2)
{
while(s[x1] == '0' && x1 <= y1) x1++;
while(s[x2] == '0' && x2 <= y2) x2++;
if(x1 > y1) return true;
if(x2 > y2) return false;
if(y1 - x1 > y2 - x2) return false;
if(y1 - x1 < y2 - x2) return true;
while(x1 <= y1){
if(s[x1] > s[x2]) return false;
if(s[x1] < s[x2]) return true;
x1++;x2++;
}
return false;
}
int main()
{
while(~scanf("%s",s+1)){
int n = strlen(s+1);
if(s[1] == '0' && n == 1){
break;
}
dp[1] = 1;
for(int i = 2;i <= n;i++){
dp[i] = i;
for(int j = i-1;j >= 1;j--){
if(check(j-dp[j]+1,j,j+1,i)){ //前一个数和当前这个数作比较
dp[i] = i-j; //如果前面的数严格小于当前数说明找到了最小的
break;
}
}
}
int lst = n - dp[n] + 1;
dp[lst] = dp[n];
for(int i = lst - 1;i >= 1;i--){
if(s[i] == '0'){ //遇到0就更新后
dp[i] = dp[i+1] + 1; //更新后面一个数的长度
continue;
}
for(int j = lst;j >= 1;j--){ //倒过来找,从最后一个数出发
if(check(i,j-1,j,j+dp[j]-1)){ //[i,j-1]是当前这个数,[j,j+dp[j]-1]是后面一个数
dp[i] = j - i; //找到就算出长度
break;
}
}
}
for(int i = 1;i <= dp[1];i++){
printf("%c",s[i]);
}
int t = dp[1] + 1;
while(t <= n){
printf(",");
for(int i = t;i < t + dp[t];i++){
printf("%c",s[i]);
}
t += dp[t];
}
printf("\n");
}
return 0;
}
你来时携风带雨,我无处可避;你走时乱了四季,我久病难医。