HDU1511(Increasing Sequences)

题目传送门在这里插入图片描述

题意

给一串长度不超过80个长度的数字串,在数字串中加入若干个逗号,使得这些数字变成一个严格递增的子序列,并且要求最后一个数尽可能的小,第一个数尽可能的大,注意前导的0。

思路

两次dp,dp这个东西真的玄学,根本想不到怎么下手切入这道题,反正我是没想出来,是我太菜了,参考了网上大神的一些博客才弄懂大致的思路做法。

  1. 先写好判断两个区间哪个数字串大小的函数,要去除前导的。
//[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;				//两个数相等
}
  1. 第一次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;
		}
	}
}
  1. 第二次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;
}

你来时携风带雨,我无处可避;你走时乱了四季,我久病难医。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值