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
- vijos P1005 超长数字串 解题报告
- 【模拟】Vijos P1005 超长数字串
- vijosP1005超长数字串
- codevs 1394 数字串 贪心 解题报告
- ZZULI 1234 数字串 解题报告
- 超长数字串(1.2.7 )
- Vijos P1180选课 解题报告
- 【未出现的字串】解题报告
- 【SHLQSH数】解题报告
- 单词数解题报告
- P1005矩阵取数
- vijos p1005 奶牛浴场[ 极大化思想]
- [Vijos]1266 搜集环盖*增强版 解题报告
- HDOJ_2084:数塔 解题报告
- POJ_2676 数独解题报告
- 数的划分解题报告
- 【数论】MMT数解题报告
- HDOJ2072单词数 解题报告
- 系列:1到1000中包含8的个数
- Kubernetes1.6安装指南 (二进制文件方式)
- 二叉树的三种遍历(前序,中序,后序)
- STL的next_permutation函数
- Java基本数据类型转换
- vijos P1005 超长数字串 解题报告
- Mask RCNN in TensorFlow
- 关于js中两种定时器的设置及清除
- 系统监控利器-dstat命令(1)
- 面试准备
- HDU6027-Easy Summation-简单数学
- 银行家算法
- 387. First Unique Character in a String
- Androidannotations框架使用(二)——(AA框架简单使用)