现在的位置: 首页 > 综合 > 正文

poj 1239 increasing sequence (zoj 1499) 分步动态规划

2011年10月08日 ⁄ 综合 ⁄ 共 6871字 ⁄ 字号 评论关闭

/*
 * 给定一个长度不大于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;

}

 

 

 

 

 

 

 

抱歉!评论已关闭.