vijos P1005 超长数字串 解题报告

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());
        }
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值