【33.28%】【BZOJ 1195】[HNOI2006]最短母串
Submit: 1208 Solved: 402
[Submit][Status][Discuss]
Description
给定n个字符串(S1,S2,„,Sn),要求找到一个最短的字符串T,使得这n个字符串(S1,S2,„,Sn)都是T的子串。
Input
第一行是一个正整数n(n<=12),表示给定的字符串的个数。以下的n行,每行有一个全由大写字母组成的字符串。每个字符串的长度不超过50.
Output
只有一行,为找到的最短的字符串T。在保证最短的前提下,如果有多个字符串都满足要求,那么必须输出按字典序排列的第一个。
Sample Input
ABCD
BCDABC
Sample Output
【题解】
(看不懂的话就先看代码再看题解)
给出n个串。要找一个最短的串。满足n个所给的串都是这个串的子串。
设f[i][j]表示已经加入的字符串集合的状态为i,最后一个加入到该集合中的字符串的序号为j.
这里的加入集合过程。可以想象成把一个字符串拼接在另一个字符串后面。但是不同的是,这个拼接的过程有可能不会增加字符串的长度。即另一个字符串包括了这个字符串。右或者,拼接的时候长度不一定是两个字符串的长度之和。因为可能前一个字符串的后面有一部分和这个新加入的字符串的前面一样。
根据这个规则处理出cost[i][j]表示把j这个字符串"接到"i这个字符串后面会增加多少长度。
转移f[i][j]的时候。先枚举i这个状态的最后一个字符串是什么。
然后枚举哪一个字符串是这个i状态没有的。就尝试进行拼接(如果答案更优);
然后因为要字典序最小。在更新的过程中还要记录每个f[i][j]所代表的字符串是什么。不时地还要用strcmp进行比较。
然后是hash函数的部分。那个函数用了比价高的进制,以此来记录某段序列。第一眼看过去就能发现p[50]绝对会超unsigned int 的范围。但是超过之后是不会报错的。它会变小一点。然后超过了范围又变大。
可以观察一下p。
然后就是用加减来获取一个字符串里面某段区间的字符的hash值。
以此来判断是否有交集。或者说另一个串在前一个串的里面。
【代码】
#include <cstdio> #include <cstring> unsigned int p[51]; struct data2 { char s[51]; int len; int hash[51]; unsigned int get_key(int begin, int end)//获取字符串里面[begin,end]区间的hash字符的hash值 { return hash[end] - hash[begin - 1] * p[end - begin + 1]; } }; data2 a[13]; int n,INF; int cost[13][13]; int f[4096][13]; char temp[601]; char fa[4096][13][601]; int min(int x, int y) { return x > y ? y : x; } int sear_ch(int x, int y)//判断x和y拼接在一起要增加多少长度 { int len1 = a[x].len, len2 = a[y].len; int s = min(len1, len2); int ret = 0; bool flag = 0; if (len1 >= len2) flag = 1; if (flag)//这是字符串y为x的子串则不会增加强度 { for (int i = 1; i <= len1 - len2 + 1; i++) { if (a[x].get_key(i, i + len2 - 1) == a[y].hash[len2]) return -1; } } for (int i = 1;i <= s;i++)//如果不是子串就找交集的部分 if (a[x].get_key(len1 - i + 1, len1) == a[y].hash[i]) { ret = i; } return len2 - ret; } void input_data() { scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%s", a[i].s+1); a[i].len = strlen(a[i].s + 1); for (int j = 1; j <= a[i].len; j++)//这个hash函数超级奇怪的。。。 a[i].hash[j] = a[i].hash[j - 1] * 131 + a[i].s[j]; } for (int i = 1;i <= n;i++) for (int j = 1; j <= n; j++) { if (i == j) continue; cost[i][j] = sear_ch(i, j); } } void init() { p[0] = 1; for (int i = 1; i <= 50; i++) p[i] = p[i - 1] * 131; } void get_ans() { memset(f, 127 / 3, sizeof(f)); INF = f[0][0]; for (int i = 1; i <= n; i++) { f[1 << (i - 1)][i] = a[i].len;//只有第i个字符串 for (int j = 1; j <= a[i].len; j++)//则状态就是这个字符串 fa[1 << (i - 1)][i][j] = a[i].s[j]; fa[1 << (i - 1)][i][a[i].len + 1] = '\0'; } for (int j = 0;j <= (1<<n)-1;j++)//从小到大枚举状态 for (int i = 1;i <= n;i++)//枚举要拼到后面的字符串 if (!(j&(1 << (i - 1))))//要求没有这个字符串才能拼接 { for (int k = 1;k <= n;k++)//枚举j这个状态的最后一个字符串 if (j &(1 << (k - 1)))//如果有这个字符串 { if (cost[k][i] == -1)//是子串的情况 { if (f[j][k] < f[j | (1 << (i - 1))][k]) { f[j | (1 << (i - 1))][k] = f[j][k]; for (int l = 1; l <= f[j][k]; l++) fa[j | (1 << (i - 1))][k][l] = fa[j][k][l]; fa[j | (1 << (i - 1))][k][f[j][k] + 1] = '\0'; } else if (f[j][k] == f[j | (1 << (i - 1))][k] && f[j][k] < INF) { for (int l = 1; l <= f[j][k]; l++) temp[l] = fa[j][k][l]; temp[f[j][k] + 1] = '\0'; if (strcmp(temp + 1, fa[j | (1 << (i - 1))][k] + 1) < 0) { for (int l = 1; l <= f[j][k]; l++) fa[j | (1 << (i - 1))][k][l] = temp[l]; fa[j | (1 << (i - 1))][k][f[j][k] + 1] = '\0'; } } } else {//可以更新的情况 if (f[j][k] + cost[k][i] < f[j | (1 << (i - 1))][i]) { f[j | (1 << (i - 1))][i] = f[j][k] + cost[k][i]; for (int l = 1; l <= f[j][k]; l++) fa[j | (1 << (i - 1))][i][l] = fa[j][k][l]; for (int l = 1; l <= cost[k][i]; l++) fa[j | (1 << (i - 1))][i][f[j][k] + l] = a[i].s[l + a[i].len - cost[k][i]]; fa[j | (1 << (i - 1))][i][f[j][k] + cost[k][i] + 1] = '\0'; } else if (f[j][k] + cost[k][i] == f[j | (1 << (i - 1))][i] && f[j | (1 << (i - 1))][i] < INF) { for (int l = 1; l <= f[j][k]; l++) temp[l] = fa[j][k][l]; for (int l = 1; l <= cost[k][i]; l++) temp[f[j][k] + l] = a[i].s[l + a[i].len - cost[k][i]]; temp[f[j][k] + cost[k][i] + 1] = '\0'; if (strcmp(temp + 1, fa[j | (1 << (i - 1))][i] + 1) < 0) { for (int l = 1; l <= f[j][k] + cost[k][i]; l++) fa[j | (1 << (i - 1))][i][l] = temp[l]; fa[j | (1 << (i - 1))][i][f[j][k] + cost[k][i] + 1] = '\0'; } } } } } } void output_ans() { int mi = INF; for (int i = 1; i <= n; i++) mi = min(mi, f[(1 << n) - 1][i]); char ans[602]; for (int i = 1; i <= mi; i++)//先让字典序最大。然后找最小的字典序 ans[i] = 'z'; ans[mi + 1] = '\0'; for (int i = 1;i <= n;i++) if (f[(1 << n) - 1][i] == mi) { if (strcmp(fa[(1 << n) - 1][i] + 1, ans + 1)<0) strcpy(ans + 1, fa[(1 << n) - 1][i] + 1); } printf("%s\n", ans + 1); } int main() { //freopen("F:\\rush.txt", "r", stdin); init(); input_data(); get_ans(); output_ans(); return 0; }