vijos P1005 超长数字串 解题报告

来源:互联网 发布:图书数据加工公司招聘 编辑:程序博客网 时间:2024/05/29 14:29

vijos P1005 超长数字串 解题报告

刷OJ做到这个题,网上都查不到完整的解析,自己根据查来的代码,借用ju136的代码,归纳了一下题解,补充对实现代码的理解。

代码来源:http://blog.csdn.net/ju136/article/details/6938534

import java.io.BufferedReader;import java.io.InputStreamReader;import java.math.BigInteger;/** * * 解决思路: *  此类问题,通常暴力枚举无法AC(要么内存超出,要么处理时间超出),因此需要借助输入串的某种性质来缩小枚举范围 *  由于输入串必然是: *      (1) 一个数(全部或部分)与其前后若干个数(可以为0)(全部或部分)的组合 *      (2) 全0: *      如: *      (1) 123456,为数字1与其后5个数2、3、4、5、6的组合 *      (2) 45661234567123 为数字1234567与其前1个数1234566的后4位以及后1个数1234568的前3位的组合 *      (3) 123454124 为数字454124的前一个数454123的后3位与454124的组合 *      (4) 45661234 为数字1234566的后4位与1234567的前3位的组合 *      (5) 1000 为数字1000,与前后数字没有组合 * *      按以上的方式可以将输入串分为以下5种普遍形式: *      (1) 连续型:输入串可以拆分为[n(head), n+1, n+2, n+3, ..., n+m]的形式 *          其中,n可以由输入串连续的多位组成 *      (2) 三段型:输入串可以拆分为[head, n, n+1, n+2, n+3, ..., n+m, b] *          其中a为n-1的后若干位(可以取0位),b为n+m+1的前若干位(可以取0位) *          可以发现,连续型是三段型的特殊形式(a取0位,b取0位) *      (3) 两段型:输入串拆分为[head, next] *          其中a,b为连续的两个数的部分 *      (4) 整数型:输入串代表一个完整的数,无法拆分 *      (5) 全0型:输入串全0,此类情况比较特殊,处理也简单,因为全0只出现在10,100,1000,... * *  那么问题可以转化为: *      找出输入串符合以上4种类型(全0型的无需划分)的所有划分,并找出其中索引最小的那个划分串 *  实现:记输入串line长度为len *      (1) 首先,对每一种可能的分段(段大小segSize从1,2,...,len - 1)进行遍历,段大小为len的特殊处理 *          后面可以发现这个segSize实际上这个segSize是符合要求的划分中,一个完整数字的位数 *          也就是这个输入串是由一个位数为segSize的数(全部或部分)与其前后若干个数(可以为0、全部或部分)构成的 *      (2) 根据段大小segSize将输入串line划分为头部head(从1到segSize)和剩余部分next(从segSize-1到0) *          遍历输入串的所有可能划分 *      (3) 判断输入串的划分是否属于上述4种类型,如果属于,则是一种可行的划分,记录其索引 *          判断方法如下: *          [1] segSize大小在[1, len-1):此时包含三种可能出现的情况(三段型,两段型和连续型) *              [1.1] head长度在[1, segSize)时,此时的输入串属于三段型或二段型的划分 *                  A. 当next的长度>=段长度segSize时,属于三段型: *                      a. 此时需要先判断head与next的第一段是否是连续的两个数 *                      b. 若不是则这一划分不成立,进入下一划分。 *                      c. 若是,则继续比较next中的后面几段的数值 *                      d. 如果next中后面几段的数值是连续序列,则这是一个可行的划分,记录其索引值 *                      e. 若不是,则进入下一划分 *                  B. 当next的长度小于段长度segSize时,属于两段型: *                      a. 此时的剩余部分长度过小而不足以构成一个完整的数,必然不属于三段型 *                          因此此时必然是两段型[a, b]的序列 *                          并且 len(head)+len(next) > segSize,len(next) < segSize *                      b. 此时需要将head补充到足够的位数segSize *                          取next的前segSize-len(head)位作为head的前缀(判断是否要进位) *                          然后与next比较,若是连续则符合要求 *              [1.2] head长度等于segSize时:此时必然只能满足连续型的划分 *                  对整个输入串进行连续序列的判断,若满足连续型的性质,则记录其索引值 *          [2] segSize = len时:此时可能的情况是两段型和整数型 *              [2.1] 对于两段型的情况:遍历所有可能的两段型划分,记录最小的那个 *              [2.2] 对于整数型的情况:直接计算该数所在的位置即可 */public class LongLongSequence {    // 保存位数1到201的所有起始值和起始位置,方便计算    // ms是所需要的数组的大小,202是最小容量    // 因为输入串最多可能有200位,可能会出现在位数为201的数中    private static final int ms = 202;    // 位数1到201的起始值,分别是1, 10, 100, 1000...    private static BigInteger[] beginValue = new BigInteger[ms];    // 位数1到201的起始位置(数组索引),分别是1,10, 10 + 90 * 2, 10 + 90*2 + 900*3, ...    // 此数组需要使用DP算法来计算1到201的所有起始位置    private static BigInteger[] beginPosition = new BigInteger[ms];    // 记录当前可行划分中最小的索引,也是最后输出的结果    private static BigInteger minIndex = new BigInteger("0");    private static final BigInteger negOne = BigInteger.valueOf(-1);    private static final BigInteger nine = new BigInteger("9");    // 取得后面一个数字,输入为n,则输出n + 1    private static String getNextInt(String n) {        StringBuilder t = new StringBuilder();        int len = n.length(), sum, carry = 1;        for (int i = len - 1; i >= 0; --i) {            char num = n.charAt(i);            sum = num - 48 + carry;            carry = sum / 10;            sum %= 10;            t.insert(0, (char) (sum + 48));        }        while (carry > 0) {            sum = carry % 10;            t.insert(0, (char) (sum + 48));            carry /= 10;        }        return t.toString();    }    // 取得前一个数字,输入为n,则输出n - 1    private static String getFrontInt(String n) {        StringBuilder t = new StringBuilder();        int len = n.length(), diff, borrow = 1;        for (int i = len - 1; i >= 0; --i) {            char num = n.charAt(i);            diff = num - 48 - borrow;            if (diff < 0) {                diff += 10;                borrow = 1;            } else {                borrow = 0;            }            if (!(0 == i && diff == 0)) {                t.insert(0, (char) (diff + 48));            }        }        if (t.length() == 0) {            t = new StringBuilder("0");        }        return t.toString();    }    // 获得a与line[pos: ]相等的子串长度,没有时返回-1    private static int getEqualSubStrLen(String a, String line, int pos) {        int i;        for (i = 0; i < a.length() && (pos + i) < line.length(); ++i) {            if (a.charAt(i) != line.charAt(pos + i)) {                return -1;            }        }        return i;    }    // 判断line是否为连续数构成的序列    // 方法为:    //      取第1个段作为头部head,获得head对应的后继successor(即head+1)    //      与line[beginPos: ]比较,要么successor是line[beginPos: ]的子串    //      要么line[beginPos: ]是successor的子串,否则不是连续数序列    private static boolean stringCompareBySegSize(String line, int segSize) {        int beginPos = segSize, ret, len = line.length();        String head = line.substring(0, segSize);        while (beginPos < len) {            String successor = getNextInt(head);            ret = getEqualSubStrLen(successor, line, beginPos);            if (-1 == ret) {                return false;            } else {                beginPos += ret;                head = successor;            }        }        return true;    }    // 在计算一个数的后继时,可能存在进位的情况(后几位全为9)    // 在二段型情况的处理时需要额外判断    private static boolean isCarryOut(String a) {        int len = a.length();        for (int i = 0; i < len; ++i) {            if (a.charAt(i) != '9') return false;        }        return true;    }    //    --------------------------------------------------------------------------//    // beginValue[] 记录了位数1到201的起始值,分别是1, 10, 100, 1000..    // beginPosition[] 位数1到201的起始位置(数组索引),分别是1,10, 10 + 90 * 2, 10 + 90*2 + 900*3, ...    // 此数组需要使用DP算法来计算1到201的所有起始位置    // len[] 为位数1到201的区间中数的个数,位数为1的有9个,2的有90(99 - 10 + 1)个,3的有900(999 - 100 + 1)个...    private static void genBeginPos() {        BigInteger[] len = new BigInteger[ms];        BigInteger ten = BigInteger.ONE;        len[0] = BigInteger.ZERO;        beginPosition[0] = BigInteger.ZERO;        beginPosition[1] = BigInteger.ONE;        for (int i = 1; i < ms; ++i) {            len[i] = nine.multiply(ten);            beginValue[i] = ten;            ten = ten.multiply(BigInteger.valueOf(10));        }        // DP算法求位数为i的第一个数的位置        for (int i = 2; i < ms; ++i) {            beginPosition[i] = beginPosition[i - 1].add(len[i - 1].multiply(BigInteger.valueOf(i - 1)));        }    }    // 计算数value所在的位置    // 根据其位数确定起始位置from    // 根据value的值计算其相对于起始值的偏移量    // value的位置就等于 from + offset    private static BigInteger getPos(String value) {        int len = value.length();        BigInteger bValue = beginValue[len];        BigInteger from = beginPosition[len];        BigInteger offset = (new BigInteger(value)).subtract(bValue);        offset = offset.multiply(BigInteger.valueOf(len));        return from.add(offset);    }    // 设置当前最小的满足要求的划分的索引    private static void setMinIndex(BigInteger a) {        if (minIndex.compareTo(negOne) == 0 || minIndex.compareTo(a) > 0) {            minIndex = a;        }    }    // 判断是否全0    private static boolean isAllZero(String a) {        for (int i = 0; i < a.length(); ++i) {            if (a.charAt(i) != '0') return false;        }        return true;    }    public static void main(String[] args) throws Exception {        // 首先计算位数1到201每区间的起始值和起始位置        genBeginPos();        BufferedReader cin = new BufferedReader(new InputStreamReader(System.in));        String line;        line = cin.readLine();        // 全0情况的特殊处理        if (isAllZero(line)) {            line = "1" + line;            BigInteger pos = getPos(line);            pos = pos.add(BigInteger.ONE);            System.out.println(pos.toString());            return;        }        final int line_len = line.length();        minIndex = negOne;        // 对输入串做不同的分段,从1 到 line_len - 1        for (int segSize = 1; segSize < line_len; ++segSize) {            String head, next;            int head_len, next_len;            // 情况[1.1] headSize在[1, segSize)            for (int headSize = 1; headSize < segSize; ++headSize) {                head = line.substring(0, headSize);                head_len = head.length();                next = line.substring(headSize, line_len);                next_len = next.length();                if (next_len > 0 && next.charAt(0) == '0') continue;                // 情况[1.1.A] 三段型情形                if (next_len >= segSize) {                    // next 的第1段                    String value = next.substring(0, segSize);                    String prev = getFrontInt(value);                    boolean ret = true;                    // 判断不足segSize长度的head是否与next的第1段连续即 (value - 1) 的后len(head)位 == head                    for (int i = head.length() - 1, j = prev.length() - 1;                         i >= 0 ; --i, --j) {                        if (head.charAt(i) != prev.charAt(j)) {                            ret = false;                            break;                        }                    }                    // 相等时,继续判断next序列是否为连续序列                    if (ret) {                        boolean bNextEqualToSegmentSize = stringCompareBySegSize(next, segSize);                        if (bNextEqualToSegmentSize) {                            String beginValue = getFrontInt(value);                            BigInteger pos = getPos(beginValue);                            pos = pos.add(BigInteger.valueOf(beginValue.length() - head.length()));                            setMinIndex(pos);                        }                    }                } else { // 情况[1.1.B] 两段型[head, next]情形 len(a) + len(b) > segSize                    // 获得head的前缀,next[0, segSize - len(head)]                    String front_head = next.substring(0, segSize - head_len);                    // 特殊情况,前面的数全为9,涉及进位                    boolean bCarryOut = isCarryOut(head);                    String front_part_value;                    if (bCarryOut) {                        // 有进位时,head的前缀比原先少1                        front_part_value = getFrontInt(front_head);                    } else {                        front_part_value = front_head;                    }                    String frontValue = front_part_value + head;                    String NextValue = getNextInt(frontValue);                    boolean bIsEqual = true;                    // 比较加上前缀后的frontValue是否与next连续                    for (int i = 0, j = 0; i < NextValue.length() && j < next_len; ++i, ++j) {                        if (NextValue.charAt(i) != next.charAt(j)) {                            bIsEqual = false;                            break;                        }                    }                    // 连续的话,则为可行划分,记录较小的索引值                    if (bIsEqual) {                        BigInteger pos = getPos(frontValue);                        pos = pos.add(BigInteger.valueOf(frontValue.length() - head.length()));                        setMinIndex(pos);                    }                }            }            // 情况[1.2] 连续型情形            // 头部head的长度与segSize相同时,只能存在连续型的情况            // 对整个序列判断是否为连续序列            if (line.charAt(segSize) != '0' && line.charAt(0) != '0') {                boolean bHeadEqualToSegmentSize = stringCompareBySegSize(line, segSize);                if (bHeadEqualToSegmentSize) {                    String beginValue = line.substring(0, segSize);                    BigInteger pos = getPos(beginValue);                    setMinIndex(pos);                }            }        }        // 情况[2] 分段长度等于输入串长度        // 情况[2.1] 两段型        //  输入串划分为head和next,组合为next + head,找出最小的那个        //  依然需要判断进位的情况        for (int headSize = 1; headSize < line_len; ++headSize) {            String head = line.substring(0, headSize);            String next = line.substring(headSize, line_len);            final int next_len = next.length();            if (next_len > 0 && next.charAt(0) == '0') continue;            // 进位处理            boolean bCarryOut = isCarryOut(head);            String frontValue;            if (bCarryOut) {                frontValue = getFrontInt(next) + head;            } else {                frontValue = next + head;            }            BigInteger pos = getPos(frontValue);            pos = pos.add(BigInteger.valueOf(frontValue.length() - head.length()));            setMinIndex(pos);        }        // 情况[2.2] 整数型        // 此时整个输入串代表一个数,直接计算得到其位置即可        if (line_len > 0 && line.charAt(0) != '0') {            BigInteger pos = getPos(line);            setMinIndex(pos);        }        if (minIndex.compareTo(negOne) != 0) {            System.out.println(minIndex.toString());        }    }}
0 0
原创粉丝点击