• java实现 蓝桥杯 算法训练 Password Suspects


    问题描述
      在年轻的时候,我们故事中的英雄——国王 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。个人感觉就是在这里,才导致运算超时…

    package com.liuzhen.systemExe;
    
    import java.util.ArrayList;
    import java.util.Scanner;
    
    public class Main{
        //找出字符串arrayA[i]到arrayA[j]中的最大回文串,并返回其初始位置和长度(PS:此方法称作中心扩展法)
        public int[] getMaxReverse(char[] arrayA, int i, int j) {
            int[] result = new int[2];
            int max = 1;
            int begin = i+1;    //最大回文串的开始位置,初始化为字符i的位置
            int zero = i;      //保存i的初始值
            for(i = i + 1;i < j;i++) {
                int start = i - 1;
                int end = i + 1;
                int count = 1;
                while(start >= zero && end <= j) {    //对于此种情况,只返回奇数位回文串
                    if(arrayA[start] == arrayA[end]) {
                        start--;
                        end++;
                        count = count + 2;
                    }
                    else
                        break;
                }
                if(count > max) {
                    max = count;
                    begin = i - (count-1)/2 + 1;
                }
            }
            result[0] = begin;   //最大回文串开始位置
            result[1] = max;     //最大回文串长度
        
            return result;
        }
        //此处代码是使用Manacher算法求取最大回文串,但是在蓝桥杯练习系统中评分时,也是运行超时,所以在求最大回文串就采用上面更易理解的方法
    //    public int[] getManacher(char[] arrayA, int i, int j) {
    //        int[] result = new int[2];
    //        ArrayList<Character> listA = new ArrayList<Character>();
    //        for(int y = i;y <= j;y++) {
    //            listA.add('#');
    //            listA.add(arrayA[y]);
    //        }
    //        listA.add('#');
    //        int[] P = new int[listA.size() + 1];
    //        int mx = 0;
    //        int d = 0;
    //        for(int y = 1;y <= listA.size();y++) {
    //            if(mx > y) {
    //                P[y] = (P[2 * d - y] > (mx - y) ? (mx - y) : P[2 * d - y]);
    //            } else {
    //                P[y] = 1;
    //            }
    //            
    //            while(y + P[y] < listA.size() && y - P[y] >= 0 && listA.get(y + P[y]) == listA.get(y - P[y]))  
    //                P[y]++;
    //            
    //            if(mx < y + P[y]) {
    //                mx = y + P[y];
    //                d = y;
    //            }    
    //        }
    //        
    //        int max = 0;
    //        int tempY = 0;
    //        for(int y = 0;y < P.length;y++) {
    //            if(P[y] > max) {
    //                max = P[y];
    //                tempY = y;
    //            }
    //        }
    //        int begin = (tempY - 1)/2;
    //        begin = begin - (max - 1) / 2 + i + 1;
    //        result[0] = begin;
    //        result[1] = max - 1; 
    //        return result;
    //    }
        //使用KMP模式匹配,计算next函数值
        public int[] getNext(char[] arrayB) {
            int[] next = new int[arrayB.length + 1];
            int j = 0;
            for(int i = 1;i < arrayB.length;i++) {
                while(j > 0 && arrayB[i] != arrayB[j]) j = next[j];
                if(arrayB[i] == arrayB[j]) j++;
                next[i+1] = j;
            }
            return next;
        }
        //使用KMP算法,找出字符数组arrayB,在arrayA中第一次出现匹配的位置
        public int getKmpFirstPosition(char[] arrayA, char[] arrayB) {
            int position = -1;
            int j = 0;
            int[] next = getNext(arrayB);
            for(int i = 0;i < arrayA.length;i++) {
                while(j > 0 && arrayA[i] != arrayB[j]) j = next[j];
                if(arrayA[i] == arrayB[j])
                    j++;
                if(j == arrayB.length) {
                    position = i - j + 1;
                    break;
                }
            }
            return position;
        }
        
        public void printResult(String A) {
            //当选定整数X = 0时,输出第一行为1,此时只需在A中直接找到有段最长回文串即可,这时的密码最长
            char[] arrayA = A.toCharArray();
            int[] result0 = getMaxReverse(arrayA, 0, arrayA.length-1);
            int maxLen0 = result0[1];   //获得此时回文串的最大长度
            //当X != 0时,最长密码其尾部一定在输入的字符串尾部,即 suffix不为空
            int j = arrayA.length - 1;
            int maxLen = 0;     //用于计算最长密码,初始化为0
            int position1 = 0;
            int position2 = 0;
            int position3 = 0;
            int len1 = 0;
            int len2 = 0;
             for(int lenS = 1;lenS < arrayA.length/2;lenS++) {
                char[] tempS = new char[lenS];
                for(int a = 0;a < lenS;a++) 
                    tempS[a] = arrayA[j-a];
                if(getKmpFirstPosition(arrayA, tempS) == -1) {
                    continue;
                } 
                int tempPosition1 = getKmpFirstPosition(arrayA, tempS) + 1;
                int endPosition1 = tempPosition1+lenS;
                int startPosition3 = arrayA.length-lenS;
                
                if(startPosition3 <= endPosition1)
                    continue;
        
                int[] result = getMaxReverse(arrayA,tempPosition1+lenS-1,j-lenS);
                int tempLen2 = result[1];
                int tempPosition2 = result[0];
                
                if(lenS * 2 + tempLen2 > maxLen) {
                    position1 = tempPosition1;
                    position2 = tempPosition2;
                    position3 = j - lenS + 2;
                    len1 = lenS;
                    len2 = tempLen2;
                    maxLen = lenS * 2 + tempLen2;
                }
            }
             if(maxLen0 >= maxLen) {  
                 System.out.println("1");
                  System.out.println(result0[0]+" "+result0[1]);
             } else {
                 System.out.println("3");
                 System.out.println(position1+" "+len1);
                 System.out.println(position2+" "+len2);
                 System.out.println(position3+" "+len1);
             }
        }
        
        public static void main(String[] args){
            Main test = new Main(); 
            Scanner in = new Scanner(System.in);
        //    System.out.println("请输入一个字符串:");
            String A = in.nextLine();
            test.printResult(A);
        }
    }
    

    大概思路
    d[u][len][state]表示当前递归到了这样一个状态:
    1.当前密码串已经确定的长度是len
    2.用字典树对已确定的密码串进行匹配,到当前状态时,字典树匹配到的结点是u
    3.如果state的二进制数是00001010B,假设密码子串有5个,则表示当前状态已经出现的密码子串有按输入顺序的第2、4个。
    这里实际是一个状态压缩,将所有密码的可能压缩到了三个参数上。

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.util.concurrent.ConcurrentLinkedQueue;
    
    /**
     * 
     * 这个题不是原题,原题来自<a href="https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=524&page=show_problem&problem=3517">UVALive</a>
     * 方法是使用AC自动机(要理解AC自动机需要先掌握KMP算法和字典树) 以及DP算法
     * 参考实现<a href="https://blog.csdn.net/accelerator_/article/details/38758557">CSDN</a>
     */
    public class Main {
    	
    	public static void main(String[] args) {
    		
    		int n = 0;
    		int m = 0;
    		try {
    			// 读取数据
    			BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    			String[] nm = br.readLine().split(" ");
    			n = Integer.parseInt(nm[0]);
    			m = Integer.parseInt(nm[1]);
    			// 构建AC自动机
    			AC ac = new AC(n, m);
    			for(int i = 0; i < m; i ++) {
    				ac.insert(br.readLine(), i);
    			}
    			br.close();
    			ac.build();
    			long ans = ac.dfs(0, 0, 0);
    			System.out.println(ans);
    			if(ans <= 42)
    				ac.print(0, 0, 0);
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}
    	
    }
    
    class AC{
    
    	int maxWord = 105; // AC自动机最大记录字母数,最大等于m*字符串最大长度=100,但是还有根节点,不妨设105
    	int dicLen  = 26;  // 每一个结点的最大子结点数,即有多少个字母
    	int maxLen  = 30;  // 最大密码长度 这里也是个点,不知道为什么写25会出错
    	int state   = (1<<10) + 5;      // 1 << M,实际上是一个用二进制表示的状态集,“1”位表示这个词已经出现过了
    	long[][][] dp = new long[maxWord][maxLen][state]; // 深度搜索、动态规划的矩阵 设d[u][len][state]表示最后一个是字典树的u结点 已选长度为len,状态为state的剩余种数
    	int[] fail  = new int[maxWord]; // 状态转移表
    	int[] val   = new int[maxWord]; // 用一个二进制化的整数表示当前已经出现了多少个词 全0表示当前没有出现词 全1表示字典中的词全出现了
        int[] out   = new int[dicLen];  // 输出用
    	
    	public int[][] trie = new int[maxWord][dicLen]; // 字典树
    	private int nodeIndex = 1; // 当前的下标 0表示根结点
    	
    	int n;
    	int m;
    	public AC(int n, int m) {
    		this.n = n;
    		this.m = m;
    		for(int i = 0; i < maxWord; i ++) {       // 对数组全部赋-1,-1表示没有搜索过,0表示搜索过但是没有匹配项
    			for(int j = 0; j < maxLen; j ++) {
    				for(int k = 0; k < state; k ++) {
    					dp[i][j][k] = -1;
    				}
    			}
    		}
    	}
    	
    	public void insert(String word, int v) {
    		int pre = 0;
    		for (int i = 0; i < word.length(); i ++) {
    		    int code = toInteger(word.charAt(i));
    		    if (trie[pre][code] == 0) { // 如果字典树中不存在这个结点
    		    	trie[pre][code] = nodeIndex ++;   // 增加一个结点 记录这个值 
    		    }
    		    pre = trie[pre][code];
    		}
    		val[pre] |= (1 << v);  // 标记状态 如果AC自动机经过pre结点,则val[pre]记录的词都出现在了字符串中
    	}
    	
    	/**构建next列表*/
    	public void build() {
    		ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<Integer>();    
    		// java自带的队列就是LinkedList 但LinkedList是线程不安全的 这里必需用线程安全的 因为有递归调用问题
    		
    		// 把root的所有的子结点加入到队列中,并将这些结点的fail指向根结点
    		fail[0] = 0;
    		for (int c = 0; c < dicLen; c ++) {
    		    int u = trie[0][c];
    		    if (u != 0) {
    		    	fail[u] = 0;
    		    	queue.add(u);
    	    	}
    		}
    		
    		while (!queue.isEmpty()) {
    			
    			// 弹出一个结点
    		    int r = queue.poll();
    		    // 遍历下面的每一个结点
    		    for (int c = 0; c < dicLen; c ++) {
    				int u = trie[r][c];
    				if (u == 0) { // 如果字典树中不存在当前遍历的结点
    				    trie[r][c] = trie[fail[r]][c]; // 子结点指向父节点失败结点的对应子结点
    				    continue;
    				}
    				queue.add(u);
    				int v = fail[r];
    				while (v != 0 && trie[v][c] == 0)  // 如果还存着fail结点,并且这个结点的对应子结点却不存在,实际就是访问所有fail链
    					v = fail[v];
    				fail[u] = trie[v][c];              // 要么存在 要么为0
    				val[u] |= val[fail[u]];            // 当前遍历这个结点的状态集等于 当前状态集 并上 失败结点指向的状态集
    		    }
    		}
    	}
    	
    	public long dfs(int now, int len, int st) {
    		if (dp[now][len][st] != -1)  // 如果已经搜索过这个结点,直接返回
    			return dp[now][len][st];
    		dp[now][len][st] = 0;        // 否则,开始搜索这个结点
    		if (len == n) {
    		    if (st == (1 << m) - 1)  // 假设输入的m是3,则1 << m后减1得 000...000111,表示3个词都出现了
    		    	return dp[now][len][st] = 1;
    		    return dp[now][len][st] = 0;
    		}
    		for (int i = 0; i < dicLen; i++)
    			dp[now][len][st] += dfs(trie[now][i], len + 1, st|val[trie[now][i]]);
    		return dp[now][len][st];
    	}
    
    	public void print(int now, int len, int st) {
    		if (len == n) {
    		    for (int i = 0; i < len ; i ++)
    		    	System.out.print((char)(out[i] + 'a'));
    		    System.out.println();
    		    return;
    		}
    		for (int i = 0; i < dicLen; i ++) { // 递归查找结果
    		    if (dp[trie[now][i]][len + 1][st|val[trie[now][i]]] > 0) {
    				out[len] = i;
    				print(trie[now][i], len + 1, st|val[trie[now][i]]);
    		    }
    		}
        }
    	
    	public int toInteger(char c) {
    		return c - 'a';
    	}
    	
    }
    
    
  • 相关阅读:
    Building a Space Station POJ
    Networking POJ
    POJ 1251 Jungle Roads
    CodeForces
    CodeForces
    kuangbin专题 专题一 简单搜索 POJ 1426 Find The Multiple
    The Preliminary Contest for ICPC Asia Shenyang 2019 F. Honk's pool
    The Preliminary Contest for ICPC Asia Shenyang 2019 H. Texas hold'em Poker
    The Preliminary Contest for ICPC Asia Xuzhou 2019 E. XKC's basketball team
    robotparser (File Formats) – Python 中文开发手册
  • 原文地址:https://www.cnblogs.com/a1439775520/p/13078364.html
Copyright © 2020-2023  润新知