* 给定一个长度不大于80的数字序列如 01000102
* 3456
* 添加适当的逗号,使得分割的数字严格升序
* 要求给出使得最后数字最小的分割方法允许001,002,即0开头
* 如果两个不同分割使得最后的数字大小相同,取第一个数字较大的序列,如果
* 再相同取第二个数字较大的,如此...
* 3546
* 35,46 而不是 3,5,46 3,5,46 的分割index 对应 -1 0 1 4
* 100000101
* 100,000101
*
* 思路
* 1.首先不考虑不同序列,相同的最小的最后数字情况,仅仅求出一条使得最后数字最小的分割路径
*
* 动态规划
* 一条符合条件的序列,如果去掉最后的数字,仍然是符合条件的序列,即子问题仍然符合条件
* 从前到后依次计算到当前位置的最小序列。每次计算的时候用计算所有可能的最后数字,并利用前面
* 已经计算好的前面位置的最小序列。
*
* 2.处理35,46 3,5,46问题
* 开始考虑上面求出的最小序列
* 43432111678
* 4,34,321,11678
* 应该是 43,4321,1168
* 43444543378
* 4,34,44,54,3378 43, 44, 454, 3378
*
* 本质上思路1只是解决了最后的数字是什么,对于去掉最后数字x的序列,现在要将其划分使得前面的数字尽可能的大,
* 同时符合递增条件,而且最后数字小于x。
* 第二步符合逆向动态规划,可对剩下序列从右到左动态规划,因为去掉首数字后的字串仍需要符合最优要求,符合最优子问题。但是需要特别注意的是,步骤1,虽然求得最小的最后数字但是最后一个划分位置不一定确定,因为0的存在,使得083 = 83,这是本题最大的陷阱,考虑第一次dp,后x00,083的划分,需要考虑 x00,083和x0,0083,x,00083 这3种可能的划分情况,尾数分别取083,0083,00083。当前采用的方法是简单的对于所有的可能序列x00, x0, x都计算去除尾数的最优分割并比较前面序列取其中最优的,也许有更好的方法。
* 3.处理特殊情况 000013 00001,3 而不是 0000,1,3 注意000 不划分
* 即输入数据开头是0的特殊情况, 这个0很别扭,注意这里处理的时候都把开头0的影响去掉如
* 0003按3处理,它的长度为1
*4. 实现细节问题,不要试图将字符转换为整型如int,long long,因为80个字符的输入规模,涉及大整数处理了,直接按字符数组处理,写大小比较函数即可。 5. 另外,本题目也可以用递归搜索解决,但是代价较大尤其1步骤有很多重复子问题,不过1步骤动态规划找到最小尾数后,第二步可以用递归搜索因为有大量情况可以剪枝,所以时间代价虽然比dp大但是也可以接受。
* */
#include <iostream>
using namespace std;
char a[90]; //the initial number string
//对于 12,34 b[3] = 2 index_last = 1 b[1] = 0
int index_last; //求得的最后数字的大小,和分割点标记 index_last = b[strlen(a) - 1] - 1,最后一个数字a[index_last + 1:len_a - 1]
int path_now[90];
int path_best[90];
int cur = 1;
int cur_best = 1;
int b[90]; //b[i]存放到i位置为止的序列,按照题目要求最后一个数字的最小值(存前一坐标如2则值为a[2:i])
int len_a; //length of char array a
//return true only when a[s1,e1] < a[s2,e2]
bool IsLess(int s1, int e1, int s2, int e2)
{
if (s1 < 0)
return true;
while(s1 < e1 && a[s1] == '0') //delete the beginning 0
s1++;
while(s2 < e2 && a[s2] == '0')
s2++;
int len1 = e1 - s1 + 1;
int len2 = e2 - s2 + 1;
if (len1 < len2)
return true; //a[s1,e1] < a[s2,e2]
if (len1 > len2)
return false; //a[s1,e1] > a[s2,e2]
// len1 == len2
for (int i = 0; i < len1; i++) {
if (a[s1 + i] < a[s2 + i]) //a[s1,e1] < a[s2,e2]
return true;
if (a[s1 + i] > a[s2 + i]) //a[s1,e1] > a[s2,e2]
return false;
}
return false; //a[s1,e1] == a[s2,e2]
}
//return 1 a[s1,e1] < a[s2,e2]
//return 0 a[s1,e1] > a[s2,e2]
//return -1 a[s1,e1] == a[s2,e2]
int cmp(int s1, int e1, int s2, int e2)
{
if (s1 == s2 && e1 == e2)
return -1;
while(s1 < e1 && a[s1] == '0') //delete the beginning 0
s1++;
while(s2 < e2 && a[s2] == '0')
s2++;
int len1 = e1 - s1 + 1;
int len2 = e2 - s2 + 1;
if (len1 < len2)
return 1; //a[s1,e1] < a[s2,e2]
if (len1 > len2)
return 0; //a[s1,e1] > a[s2,e2]
for (int i = 0; i < len1; i++) {
if (a[s1 + i] < a[s2 + i]) //a[s1,e1] < a[s2,e2]
return 1;
if (a[s1 + i] > a[s2 + i]) //a[s1,e1] > a[s2,e2]
return 0;
}
return -1; //a[s1,e1] == a[s2,e2]
}
//找到最后一个数字,其数字大小存储在b[strlen(a) - 1]中
//what we store is b[] and index_last
void FindLast()
{
int start = 0;
while (a[start] == '0') //处理起始数字开头为0的情况,deal with 00001 treat as 1 ignore the starting 0
start++;
for (int i = start; i < len_a; i++) {
int j;
for (j = i; j > start; j--) { //对于每个位置i,求它的可能的最小的最后数字
if (IsLess(b[j - 1], j - 1, j, i)) { // if a[ b[j - 1] : j - 1] < a[j: i]
b[i] = j;
break;
}
}
if (j == start) {
b[i] = start;
}
if (i == (len_a - 1)) {
if (start != 0 && j == start) //00001
index_last = -1;
else
index_last = j - 1;
return;
}
}
index_last = -1; //like 0000 do not go into for loop
}
void PrintData(int start, int end);
//执行后将会得到前面的分割点坐标,并且cur指向下一个分割位置
//在已知最后最小尾数的情况下,递归搜索解法求解前面的分割,结合剪枝
bool FindSequence(int start, int end, int lstart, int lend, int path[])
{
while (start < end && a[start] == '0') //这个很重要不能少,有了这个预处理则长度大的就大,否则如0003与4影响判断
start++;
if (IsLess(start, end, index_last + 1, len_a - 1) &&
IsLess(lstart, lend, start, end)) {
return true;
}
for (int j = (start + (end + 1)) / 2; j > start; j--) {
//for (int j = end; j > start; j--) {
if (!IsLess(start, j - 1, index_last + 1, len_a - 1))
continue;
path[cur++] = j - 1;
if (IsLess(lstart, lend, start, j - 1) && FindSequence(j , end, start, j - 1, path))
return true;
--cur;
}
return false; //will always find so will never go here
}
//作用同上,反向dp解法,求去掉最小尾数的前面串的最优分割
//返回path的尾座标
int FindSequence(int start, int end, int path[])
{
int c[90]; //反向dp时记录值,存从右向左到当前的首数字最大值,记录例如c[5] = 7 意味者从5-end的最优序列的首元素为a[5:7]
for (int i = end; i >= start; i--) { //对于i:end找一个可能的最大首数字
if (a[i] == '0' && i != end) { //0可以与前面的合并
c[i] = c[i + 1];
continue;
}
if (IsLess(i, end, index_last + 1, len_a -1)) { //结束点,如果当前串整个长度数字仍然小于原串尾数
c[i] = end;
continue;
}
for (int j = end - 1; j >= i; j--) { //动态规划,试探找最大的首数字,子问题也是最优被利用
if (IsLess(i, j, j + 1, c[j + 1])) {
c[i] = j;
break; //ok we have find it ,need to break ,do not forget
}
}
}
//now we will finsh the path 1,23,45,67
//path -1,0,2,4
path[0] = -1;
int i = start; //actually start will always be 0 here
int j = 1;
while (i <= end) {
path[j++] = c[i];
i = c[i] + 1;
}
return j - 1; //since j++ need to - 1
}
bool IsBetterPath() {
for (int i = 0; i < cur && i < cur_best; i++) {
int status = cmp(path_now[i] + 1, path_now[i + 1], path_best[i] + 1, path_best[i + 1]);
if (status == 0) // >
return true;
else if (status == 1) // <
return false;
}
return false; //will not go to here since two path will be different
}
void CopyPath() {
for (int i = 0; i <= cur; i++) {
path_best[i] = path_now[i];
}
}
/* print result */
void PrintData(int start, int end)
{
for (int i = start; i<= end; i++)
cout << a[i];
}
void PrintResult()
{
if (index_last == -1) { //也就是说只有一个数字如 001 或者 21
cout << a << endl;
return;
}
for (int i = 0; i < cur_best; i++) {
PrintData(path_best[i] + 1, path_best[i + 1]);
cout << ",";
}
PrintData(index_last + 1, len_a - 1); //index_last + 1 , len_a - 1
cout << endl;
}
void FinishPath(int path[], int index) {
path[0] = -1;
path[cur] = index;
}
void CalcPath( ) {
len_a = strlen(a);
index_last = -1;
cur = 1; //由于是全局变量,在多次使用时要小心前面的影响 记录path的尾座标
cur_best = 1;
//anlysis string to find last minimal num
FindLast();
//find the sequnece before the last minimal num
FinishPath(path_best, index_last); //1,21 need this
if (index_last >= 1) {
//FindSequence(0, index_last, -1, -1, path_best); //find by rec
//cur_best = cur;
//FinishPath(path_best, index_last);
cur_best = FindSequence(0, index_last, path_best); //find by dp
int tmp_index = index_last;
//由于最后一个数字有可能前面带0,如取83但是前面有0,要考虑到最后数字改取083的情况
//当前的做法是考虑最后数字的所有可能,再此基础上求得前面的path取最优的path,同时
//也就最终确定了最后数字的分割位置index_last可能会向前移动
while (a[tmp_index] == '0') {
if (IsLess(b[tmp_index - 1], tmp_index - 1, index_last + 1, len_a - 1)) {
//cur = 1;
//FindSequence(0, tmp_index - 1, -1, -1, path_now);
//FinishPath(path_now, tmp_index - 1);
cur = FindSequence(0, tmp_index - 1, path_now);
if (IsBetterPath()) {
CopyPath();
cur_best = cur;
index_last = tmp_index - 1;
}
}
tmp_index--;
}
}
}
int main(int argc, char *argv[])
{
while (1) {
cin >> a;
if (a[0] == '0' && a[1] == '\0')
break;
CalcPath();
PrintResult();
}
return 0;
}