问题描述

  在年轻的时候,我们故事中的英雄——国王 Copa——他的私人数据并不是完全安全地隐蔽。对他来说是,这不可接受的。因此,他发明了一种密码,好记又难以破解。后来,他才知道这种密码是一个长度为奇数的回文串。

Copa 害怕忘记密码,所以他决定把密码写在一张纸上。他发现这样保存密码不安全,于是他决定按下述方法加密密码:他选定一个整数 X ,保证 X 不小于 0 ,且 2X 严格小于串长度。然后他把密码分成 3 段,最前面的 X 个字符为一段,最后面的 X 个字符为一段,剩余的字符为一段。不妨把这三段依次称之为 prefix, suffix, middle 。显然, middle 的长度为一个大于 0 的奇数,且 prefix 、 suffix 的长度相等。他加密后的密码即为 A + prefix + B + middle + C + suffix ,其中 A 、 B 、 C 是三个由 Copa 选定的字符串,且都有可能为空, + 表示字符串相连。

许多年过去了。Copa 昨天找到了当年写下加密后字符串的那张纸。但是,Copa 把原密码、A、B、C 都忘了。现在,他请你找一个尽量长的密码,使得这个密码有可能被当年的 Copa 发明、加密并写下。

输入格式

  输入包含一个只含有小写拉丁字母的字符串,长度在 1 到 10^5 之内。

输出格式

  第一行包含一个整数 k ,表示你找到的原密码分成的 3 个部分中有多少个非空字符串。显然 k in {1, 3} 。接下来 k 行,每行 2 个用空格分开的整数 x_i l_i ,表示这一部分的起始位置和长度。要求输出的 x_i 递增。

起始位置 x_i 应该在 1 到加密后的字符串长度之间。 l_i 必须是正整数,因为你只要输出非空部分的信息。 middle 的长度必须为奇数。

如果有多组答案,任意一组即可。提示:你要最大化的是输出的 l_i 的总和,而不是 k 。

样例输入

abacaba

样例输出

1

1 7

样例输入

axbya

样例输出

3

1 1

2 1

5 1

样例输入

xabyczba

样例输出

3

2 2

4 1

7 2

数据规模和约定

  对于 10% 的数据: n <= 10

对于 30% 的数据: n <= 100

对于 100% 的数据: n <= 100000

存在 20% 的数据,输出文件第一行为 1 。

刚开始四十分的思路(下面有AC代码)

输入一段字符串组成为: A + prefix + B + middle + C + suffix 。现在要求取 prefix + middle + suffix ,由于题目说:他选定一个整数 X ,保证 X 不小于 0 ,且 2X 严格小于串长度。那么X可取0。即当X = 0时,也只有一段——middle。

现在要求取最长的密码串:

(1)首先求取原字符串中的最长回文串长度,用maxLen0表示。

(2)由于suffix在字符串尾部,先将suffix反转,然后使用KMP算法,在原字符串中找到第一次与suffix匹配的字符串,记录suffix长度文len1。然后,求取者两端匹配字符串中间剩余的一段字符串的最大回文串的长度len2。此时,求取的密码长度maxLen = 2*len1 + len2。

在这种情况下,影响maxLen长度的因素就是初始选择的len1长度,len1长度变化,也会导致len2长度的变化。所以,我这里采用1 <= len1 < 原始字符串一半的方法来枚举,求取其中的最大maxLen。个人感觉就是在这里,才导致运算超时…

  1. package com.liuzhen.systemExe;
  2. import java.util.ArrayList;
  3. import java.util.Scanner;
  4. public class Main{
  5. //找出字符串arrayA[i]到arrayA[j]中的最大回文串,并返回其初始位置和长度(PS:此方法称作中心扩展法)
  6. public int[] getMaxReverse(char[] arrayA, int i, int j) {
  7. int[] result = new int[2];
  8. int max = 1;
  9. int begin = i+1; //最大回文串的开始位置,初始化为字符i的位置
  10. int zero = i; //保存i的初始值
  11. for(i = i + 1;i < j;i++) {
  12. int start = i - 1;
  13. int end = i + 1;
  14. int count = 1;
  15. while(start >= zero && end <= j) { //对于此种情况,只返回奇数位回文串
  16. if(arrayA[start] == arrayA[end]) {
  17. start--;
  18. end++;
  19. count = count + 2;
  20. }
  21. else
  22. break;
  23. }
  24. if(count > max) {
  25. max = count;
  26. begin = i - (count-1)/2 + 1;
  27. }
  28. }
  29. result[0] = begin; //最大回文串开始位置
  30. result[1] = max; //最大回文串长度
  31. return result;
  32. }
  33. //此处代码是使用Manacher算法求取最大回文串,但是在蓝桥杯练习系统中评分时,也是运行超时,所以在求最大回文串就采用上面更易理解的方法
  34. // public int[] getManacher(char[] arrayA, int i, int j) {
  35. // int[] result = new int[2];
  36. // ArrayList<Character> listA = new ArrayList<Character>();
  37. // for(int y = i;y <= j;y++) {
  38. // listA.add('#');
  39. // listA.add(arrayA[y]);
  40. // }
  41. // listA.add('#');
  42. // int[] P = new int[listA.size() + 1];
  43. // int mx = 0;
  44. // int d = 0;
  45. // for(int y = 1;y <= listA.size();y++) {
  46. // if(mx > y) {
  47. // P[y] = (P[2 * d - y] > (mx - y) ? (mx - y) : P[2 * d - y]);
  48. // } else {
  49. // P[y] = 1;
  50. // }
  51. //
  52. // while(y + P[y] < listA.size() && y - P[y] >= 0 && listA.get(y + P[y]) == listA.get(y - P[y]))
  53. // P[y]++;
  54. //
  55. // if(mx < y + P[y]) {
  56. // mx = y + P[y];
  57. // d = y;
  58. // }
  59. // }
  60. //
  61. // int max = 0;
  62. // int tempY = 0;
  63. // for(int y = 0;y < P.length;y++) {
  64. // if(P[y] > max) {
  65. // max = P[y];
  66. // tempY = y;
  67. // }
  68. // }
  69. // int begin = (tempY - 1)/2;
  70. // begin = begin - (max - 1) / 2 + i + 1;
  71. // result[0] = begin;
  72. // result[1] = max - 1;
  73. // return result;
  74. // }
  75. //使用KMP模式匹配,计算next函数值
  76. public int[] getNext(char[] arrayB) {
  77. int[] next = new int[arrayB.length + 1];
  78. int j = 0;
  79. for(int i = 1;i < arrayB.length;i++) {
  80. while(j > 0 && arrayB[i] != arrayB[j]) j = next[j];
  81. if(arrayB[i] == arrayB[j]) j++;
  82. next[i+1] = j;
  83. }
  84. return next;
  85. }
  86. //使用KMP算法,找出字符数组arrayB,在arrayA中第一次出现匹配的位置
  87. public int getKmpFirstPosition(char[] arrayA, char[] arrayB) {
  88. int position = -1;
  89. int j = 0;
  90. int[] next = getNext(arrayB);
  91. for(int i = 0;i < arrayA.length;i++) {
  92. while(j > 0 && arrayA[i] != arrayB[j]) j = next[j];
  93. if(arrayA[i] == arrayB[j])
  94. j++;
  95. if(j == arrayB.length) {
  96. position = i - j + 1;
  97. break;
  98. }
  99. }
  100. return position;
  101. }
  102. public void printResult(String A) {
  103. //当选定整数X = 0时,输出第一行为1,此时只需在A中直接找到有段最长回文串即可,这时的密码最长
  104. char[] arrayA = A.toCharArray();
  105. int[] result0 = getMaxReverse(arrayA, 0, arrayA.length-1);
  106. int maxLen0 = result0[1]; //获得此时回文串的最大长度
  107. //当X != 0时,最长密码其尾部一定在输入的字符串尾部,即 suffix不为空
  108. int j = arrayA.length - 1;
  109. int maxLen = 0; //用于计算最长密码,初始化为0
  110. int position1 = 0;
  111. int position2 = 0;
  112. int position3 = 0;
  113. int len1 = 0;
  114. int len2 = 0;
  115. for(int lenS = 1;lenS < arrayA.length/2;lenS++) {
  116. char[] tempS = new char[lenS];
  117. for(int a = 0;a < lenS;a++)
  118. tempS[a] = arrayA[j-a];
  119. if(getKmpFirstPosition(arrayA, tempS) == -1) {
  120. continue;
  121. }
  122. int tempPosition1 = getKmpFirstPosition(arrayA, tempS) + 1;
  123. int endPosition1 = tempPosition1+lenS;
  124. int startPosition3 = arrayA.length-lenS;
  125. if(startPosition3 <= endPosition1)
  126. continue;
  127. int[] result = getMaxReverse(arrayA,tempPosition1+lenS-1,j-lenS);
  128. int tempLen2 = result[1];
  129. int tempPosition2 = result[0];
  130. if(lenS * 2 + tempLen2 > maxLen) {
  131. position1 = tempPosition1;
  132. position2 = tempPosition2;
  133. position3 = j - lenS + 2;
  134. len1 = lenS;
  135. len2 = tempLen2;
  136. maxLen = lenS * 2 + tempLen2;
  137. }
  138. }
  139. if(maxLen0 >= maxLen) {
  140. System.out.println("1");
  141. System.out.println(result0[0]+" "+result0[1]);
  142. } else {
  143. System.out.println("3");
  144. System.out.println(position1+" "+len1);
  145. System.out.println(position2+" "+len2);
  146. System.out.println(position3+" "+len1);
  147. }
  148. }
  149. public static void main(String[] args){
  150. Main test = new Main();
  151. Scanner in = new Scanner(System.in);
  152. // System.out.println("请输入一个字符串:");
  153. String A = in.nextLine();
  154. test.printResult(A);
  155. }
  156. }

大概思路

d[u][len][state]表示当前递归到了这样一个状态:

1.当前密码串已经确定的长度是len

2.用字典树对已确定的密码串进行匹配,到当前状态时,字典树匹配到的结点是u

3.如果state的二进制数是00001010B,假设密码子串有5个,则表示当前状态已经出现的密码子串有按输入顺序的第2、4个。

这里实际是一个状态压缩,将所有密码的可能压缩到了三个参数上。

  1. import java.io.BufferedReader;
  2. import java.io.IOException;
  3. import java.io.InputStreamReader;
  4. import java.util.concurrent.ConcurrentLinkedQueue;
  5. /**
  6. *
  7. * 这个题不是原题,原题来自<a href="https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=524&page=show_problem&problem=3517">UVALive</a>
  8. * 方法是使用AC自动机(要理解AC自动机需要先掌握KMP算法和字典树) 以及DP算法
  9. * 参考实现<a href="https://blog.csdn.net/accelerator_/article/details/38758557">CSDN</a>
  10. */
  11. public class Main {
  12. public static void main(String[] args) {
  13. int n = 0;
  14. int m = 0;
  15. try {
  16. // 读取数据
  17. BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
  18. String[] nm = br.readLine().split(" ");
  19. n = Integer.parseInt(nm[0]);
  20. m = Integer.parseInt(nm[1]);
  21. // 构建AC自动机
  22. AC ac = new AC(n, m);
  23. for(int i = 0; i < m; i ++) {
  24. ac.insert(br.readLine(), i);
  25. }
  26. br.close();
  27. ac.build();
  28. long ans = ac.dfs(0, 0, 0);
  29. System.out.println(ans);
  30. if(ans <= 42)
  31. ac.print(0, 0, 0);
  32. } catch (IOException e) {
  33. e.printStackTrace();
  34. }
  35. }
  36. }
  37. class AC{
  38. int maxWord = 105; // AC自动机最大记录字母数,最大等于m*字符串最大长度=100,但是还有根节点,不妨设105
  39. int dicLen = 26; // 每一个结点的最大子结点数,即有多少个字母
  40. int maxLen = 30; // 最大密码长度 这里也是个点,不知道为什么写25会出错
  41. int state = (1<<10) + 5; // 1 << M,实际上是一个用二进制表示的状态集,“1”位表示这个词已经出现过了
  42. long[][][] dp = new long[maxWord][maxLen][state]; // 深度搜索、动态规划的矩阵 设d[u][len][state]表示最后一个是字典树的u结点 已选长度为len,状态为state的剩余种数
  43. int[] fail = new int[maxWord]; // 状态转移表
  44. int[] val = new int[maxWord]; // 用一个二进制化的整数表示当前已经出现了多少个词 全0表示当前没有出现词 全1表示字典中的词全出现了
  45. int[] out = new int[dicLen]; // 输出用
  46. public int[][] trie = new int[maxWord][dicLen]; // 字典树
  47. private int nodeIndex = 1; // 当前的下标 0表示根结点
  48. int n;
  49. int m;
  50. public AC(int n, int m) {
  51. this.n = n;
  52. this.m = m;
  53. for(int i = 0; i < maxWord; i ++) { // 对数组全部赋-1,-1表示没有搜索过,0表示搜索过但是没有匹配项
  54. for(int j = 0; j < maxLen; j ++) {
  55. for(int k = 0; k < state; k ++) {
  56. dp[i][j][k] = -1;
  57. }
  58. }
  59. }
  60. }
  61. public void insert(String word, int v) {
  62. int pre = 0;
  63. for (int i = 0; i < word.length(); i ++) {
  64. int code = toInteger(word.charAt(i));
  65. if (trie[pre][code] == 0) { // 如果字典树中不存在这个结点
  66. trie[pre][code] = nodeIndex ++; // 增加一个结点 记录这个值
  67. }
  68. pre = trie[pre][code];
  69. }
  70. val[pre] |= (1 << v); // 标记状态 如果AC自动机经过pre结点,则val[pre]记录的词都出现在了字符串中
  71. }
  72. /**构建next列表*/
  73. public void build() {
  74. ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<Integer>();
  75. // java自带的队列就是LinkedList 但LinkedList是线程不安全的 这里必需用线程安全的 因为有递归调用问题
  76. // 把root的所有的子结点加入到队列中,并将这些结点的fail指向根结点
  77. fail[0] = 0;
  78. for (int c = 0; c < dicLen; c ++) {
  79. int u = trie[0][c];
  80. if (u != 0) {
  81. fail[u] = 0;
  82. queue.add(u);
  83. }
  84. }
  85. while (!queue.isEmpty()) {
  86. // 弹出一个结点
  87. int r = queue.poll();
  88. // 遍历下面的每一个结点
  89. for (int c = 0; c < dicLen; c ++) {
  90. int u = trie[r][c];
  91. if (u == 0) { // 如果字典树中不存在当前遍历的结点
  92. trie[r][c] = trie[fail[r]][c]; // 子结点指向父节点失败结点的对应子结点
  93. continue;
  94. }
  95. queue.add(u);
  96. int v = fail[r];
  97. while (v != 0 && trie[v][c] == 0) // 如果还存着fail结点,并且这个结点的对应子结点却不存在,实际就是访问所有fail链
  98. v = fail[v];
  99. fail[u] = trie[v][c]; // 要么存在 要么为0
  100. val[u] |= val[fail[u]]; // 当前遍历这个结点的状态集等于 当前状态集 并上 失败结点指向的状态集
  101. }
  102. }
  103. }
  104. public long dfs(int now, int len, int st) {
  105. if (dp[now][len][st] != -1) // 如果已经搜索过这个结点,直接返回
  106. return dp[now][len][st];
  107. dp[now][len][st] = 0; // 否则,开始搜索这个结点
  108. if (len == n) {
  109. if (st == (1 << m) - 1) // 假设输入的m是3,则1 << m后减1得 000...000111,表示3个词都出现了
  110. return dp[now][len][st] = 1;
  111. return dp[now][len][st] = 0;
  112. }
  113. for (int i = 0; i < dicLen; i++)
  114. dp[now][len][st] += dfs(trie[now][i], len + 1, st|val[trie[now][i]]);
  115. return dp[now][len][st];
  116. }
  117. public void print(int now, int len, int st) {
  118. if (len == n) {
  119. for (int i = 0; i < len ; i ++)
  120. System.out.print((char)(out[i] + 'a'));
  121. System.out.println();
  122. return;
  123. }
  124. for (int i = 0; i < dicLen; i ++) { // 递归查找结果
  125. if (dp[trie[now][i]][len + 1][st|val[trie[now][i]]] > 0) {
  126. out[len] = i;
  127. print(trie[now][i], len + 1, st|val[trie[now][i]]);
  128. }
  129. }
  130. }
  131. public int toInteger(char c) {
  132. return c - 'a';
  133. }
  134. }

java实现 蓝桥杯 算法训练 Password Suspects的更多相关文章

  1. Java实现 蓝桥杯 算法训练 猴子吃包子(暴力)

    试题 算法训练 猴子吃包子 问题描述 从前,有一只吃包子很厉害的猴子,它可以吃无数个包子,但是,它吃不同的包子速度也不同:肉包每秒钟吃x个:韭菜包每秒钟吃y个:没有馅的包子每秒钟吃z个:现在有x1个肉 ...

  2. Java实现蓝桥杯 算法训练 大等于n的最小完全平方数

    试题 算法训练 大等于n的最小完全平方数 资源限制 时间限制:1.0s 内存限制:256.0MB 问题描述 输出大等于n的最小的完全平方数. 若一个数能表示成某个自然数的平方的形式,则称这个数为完全平 ...

  3. Java实现 蓝桥杯 算法训练VIP 报数(暴力+数学)约瑟夫环问题

    试题 算法训练 报数 问题描述 现有n个同学站成一圈,顺时针编号1至n.从1号同学开始顺时针1/2报数,报到1的同学留在原地,报到2的同学退出圆圈,直到只剩一名同学为止.问最后剩下的同学编号. 输入格 ...

  4. Java实现蓝桥杯 算法训练 ALGO-15 旅行家的预算

    问题描述 一个旅行家想驾驶汽车以最少的费用从一个城市到另一个城市(假设出发时油箱是空的).给定两个城市之间的距离D1.汽车油箱的容量C(以升为单位).每升汽油能行驶的距离D2.出发点每升汽油价格P和沿 ...

  5. Java实现 蓝桥杯 算法训练 审美课

    算法训练 审美课 时间限制:1.0s 内存限制:256.0MB 提交此题 问题描述 <审美的历程>课上有n位学生,帅老师展示了m幅画,其中有些是梵高的作品,另外的都出自五岁小朋友之手.老师 ...

  6. Java实现 蓝桥杯 算法训练 多阶乘计算

    试题 算法训练 多阶乘计算 问题描述 我们知道,阶乘n!表示n*(n-1)(n-2)-21, 类似的,可以定义多阶乘计算,例如:5!!=531,依次可以有n!..!(k个'!',可以简单表示为n(k) ...

  7. Java实现 蓝桥杯 算法训练 找零钱

    试题 算法训练 找零钱 问题描述 有n个人正在饭堂排队买海北鸡饭.每份海北鸡饭要25元.奇怪的是,每个人手里只有一张钞票(每张钞票的面值为25.50.100元),而且饭堂阿姨一开始没有任何零钱.请问饭 ...

  8. Java实现 蓝桥杯 算法训练 第五次作业:字符串排序

    试题 算法训练 第五次作业:字符串排序 问题描述 输入一个小写字符串,按从小到大的顺序输出. 输入格式 bcaed 输出格式 abcde 顶格输出,中间没有空格 样例输入 一个满足题目要求的输入范例. ...

  9. Java实现 蓝桥杯 算法训练 求和求平均值

    试题 算法训练 求和求平均值 问题描述 从键盘输入10个浮点数,求出它们的和以及平均值,要求用函数实现 输入格式 测试数据的输入一定会满足的格式. 1 10 (1行10列的向量) 输出格式 要求用户的 ...

随机推荐

  1. 帝国cms列表页内容简介字段smalltext去除里面html格式代码 设置方法

    帝国cms列表页内容简介字段smalltext去除里面html格式代码帝国cms列表页调用内容简介出现html代码怎么办 近来在用帝国cms的时候,发现一个问题,在列表页调用产品简介的时候出现了这种h ...

  2. 线程和Python—Python多线程编程

    线程和Python 本节主要记录如何在 Python 中使用线程,其中包括全局解释器锁对线程的限制和对应的学习脚本. 全局解释器锁 Python 代码的执行是由 Python 虚拟机(又叫解释器主循环 ...

  3. Centos ps命令

    输出格式(ps -aux) USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND VSZ:占用的虚拟内存大小() RSS: COMMAND: 执 ...

  4. HttpServletRequest 和 HttpServletResponse详解

    用HttpServletRequest,现在整理如下,以便以后查阅 请求与响应相关的类和接口非常多,下表是主要的与请求和接口相关的类以及接口. 主要的与请求和接口相关的类及接口 方    法 说    ...

  5. 《机器学习_02_线性模型_Logistic回归》

    import numpy as np import os os.chdir('../') from ml_models import utils import matplotlib.pyplot as ...

  6. $releasever 不正确解析

    [nginx] gpgcheck=0 baseurl=http://nginx.org/packages/centos/$releasever/$basearch/ name=nginx repo 这 ...

  7. 「雕爷学编程」Arduino动手做(41)---激光接收管模块

    37款传感器与模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止37种的.鉴于本人手头积累了一些传感器和模块,依照实践出真知(一定要动手做)的理念,以学习和交流为目的,这里 ...

  8. DRF视图组件

    DRF视图组件: CVB模式继承----五层 from django.views import View # Django的View from rest_framework.views import ...

  9. Webconfig配置刷新时间,前台页面调用这个时间

    <configuration> <appSettings> <add key="webpages:Version" value="2.0.0 ...

  10. Linux下VCS2014和Verdi2015的联合使用

    VCS和Verdi是IC设计中常用的两款开发工具.VCS是Synopsys公司的产品,和大家所熟知的ModeSim一样的都是EDA仿真工具.Verdi是Nocas公司(已经被Synopsys公司收购) ...