1. 程式人生 > >第七屆藍橋杯大學生C組java決賽題目 密文搜尋

第七屆藍橋杯大學生C組java決賽題目 密文搜尋


標題:密文搜尋

福爾摩斯從X星收到一份資料,全部是小寫字母組成。
他的助手提供了另一份資料:許多長度為8的密碼列表。
福爾摩斯發現,這些密碼是被打亂後隱藏在先前那份資料中的。

請你編寫一個程式,從第一份資料中搜索可能隱藏密碼的位置。要考慮密碼的所有排列可能性。

資料格式:

輸入第一行:一個字串s,全部由小寫字母組成,長度小於1024*1024
緊接著一行是一個整數n,表示以下有n行密碼,1<=n<=1000
緊接著是n行字串,都是小寫字母組成,長度都為8

要求輸出:
一個整數, 表示每行密碼的所有排列在s中匹配次數的總和。

例如:
使用者輸入:
aaaabbbbaabbcccc
2
aaaabbbb
abcabccc

則程式應該輸出:
4
這是因為:第一個密碼匹配了3次,第二個密碼匹配了1次,一共4次。

解題思路:一看題目,“每行密碼的所有排列在s中匹配次數的總和“,我就想用全排列然後在s中索引,然後發現字母很多重複的,就會有很多重複排列,

然後我就發現可以把s分成很多長度為八的子字串,通過比較子字串中字母使用次數是不是和密碼中字母使用情況相同,如果相同,則該子字串是密碼所有排列中匹配的一種情況。程式碼如下:

import java.util.Scanner;

public class Main155 {

	private static int count = 0;

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		String s = sc.next();
		int n = sc.nextInt();
		for (int j = 0; j < n; j++) {
			char[] ps = sc.next().toCharArray();
			for (int i = 0; i < s.length(); i++) {
				//substring()方法引數的範圍必須在[0,s.length()]中
				if (i + 8 <= s.length()) check(s.substring(i, i + 8), ps);
			}
		}
		System.out.println(count);
	}

	public static void check(String s, char[] ps) {
		boolean[] f = new boolean[8];
		for (int i = 0; i < s.length(); i++) {
			for (int j = 0; j < ps.length; j++) {
				if (!f[j] && s.charAt(i) == ps[j]) {
					f[j] = true;
					break;
				}
			}
		}
		for (int j = 0; j < f.length; j++) {
			if (!f[j]) {
				return;
			}
		}
		count++;
	}
}

給出另一種官方演算法,和上面的實現思想大同小異,但是弄懂上面方法作之後,下面的演算法設計可能會讓大家獲益更多,更能體會演算法思想的奇妙:

 演算法的設計根據這樣一個特性:
 設,n個素數的積為:m
 則,m可以唯一地分解為這些素數因子。
 只要把每個字母對應一個不同的素數,包含不同字母組合的集團就可以用它們的積表示。
 只要判斷積是否相同,就知道是否包含了同一組字母。

import java.util.Scanner;

public class Main155 {

	// 隨便取26個素數,對應26個字母
	static int[] prim = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89,
			97, 101 };
	// 統計匹配個數變數;
	static int count = 0;
	// 儲存字串(第一份資料)中所有長度為8的子字串的素數積
	static long[] kkey;
	static int N = 8;

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		String s = sc.next();

		create_kkey(s);

		int n = sc.nextInt();
		int count = 0;
		for (int j = 0; j < n; j++) {
			count += find(sc.next());
		}
		System.out.println(count);
	}

	public static void create_kkey(String s) {
		// 每個字母對應一個字母組合,從倒數第七個字母開始子字串不滿足長度為N;
		kkey = new long[s.length() - N + 1];
		kkey[0] = 1;
		for (int i = 0; i < N; i++) {
			kkey[0] *= prim[s.charAt(i) - 'a'];
		}
		for (int i = 1; i < kkey.length; i++) {
			kkey[i] = kkey[i - 1] / prim[s.charAt(i - 1) - 'a'] * prim[s.charAt(i + N - 1) - 'a'];
		}
	}

	public static int find(String ps) {
		long key = 1;
		for (int i = 0; i < ps.length(); i++) {
			key *= prim[ps.charAt(i) - 'a'];
		}
		int sum = 0;
		for (int i = 0; i < kkey.length; i++) {
			if (key == kkey[i])
				sum++;
		}
		return sum;
	}
}