P3269 [JLOI2016] 字符串覆盖题解
hhh(鼠鼠第一篇黑题题解)
题目传送门
题目描述
字符串 A 有 N 个子串 B1,B2,...,Bn。如果将这 n 个子串分别放在恰好一个它在 A 中出现的位置上(子串之间可以重叠)这样 A 中的若干字符就被这 N 个子串覆盖了。问 A 中能被覆盖字符个数的最小值和最大值。
输入格式
第一行包含一个正整数 T,表示数据组数。保证 T≤10。
接下来依次描述 T 组数据,每组数据中:
第一行包含一个由小写字母组成的字符串,表示母串 A。
第二行包含一个整数 N,表示子串的个数。
接下来 N 行,每行包含一个由小写字母组成的字符串,描述子串。数据保证所有子串均在母串中出现。
输出格式
输出为 T 行,对应每组数据的答案。每行包含两个整数 Minans 和 Maxans,分别表示对应数据中能被覆盖字符数量的最小值和最大值。
输入输出样例
输入 #1
2 hello 4 he l l o abacaba 4 ab ba a c
输出 #1
4 5 4 6
说明/提示
字符串长度 A≤10000,N≤4,子串长度≤10000。
通过上面的阅读可以知道,这道题已经算黑题中还行的了。
但我们如何做这题呢?(明示*1)
为啥是字符串覆盖呢?(明示*2)
最大值最小值咋求呢?(明示*3)
非常显然:最大值最小值需要分别来算(除非是别的方法,如果没想出来这点的话……考虑一下上个班吧……)
首先呢,我们需要设一些辅助数组:n 个子串 si 的 next 数组(KMP)以及与母串 T 在每个位置的匹配情况。在优化之后,复杂度来到O(nL),时限1秒,够了。
1.最大值
遇到这种题目我们似乎无从下手,那么尝试把 n=4 作为突破口。考虑 n! 枚举钦定每个字符串出现位置按开头从左到右的顺序,那么一个贪心的想法是把出现顺序在前面的字符串尽量往前放。但这样有个问题,就是在放第 i 个字符串时有两种情况:是否与 si−1 重叠,因为两种情况都有可能成为最优解(反例容易举出)。但若确定了是哪种情况,贪心策略就保证了方案唯一:若不重叠,则越往前放越好(给剩下来的字符串留足空间);若重叠则越往后放越好(因为不劣)。因此再 2n−1 枚举相邻的两个字符串是否重叠即可。注意统计答案是不应只关注前一个字符串,因为可能出现 l1<l2<r2<l3<r3<r1 的情况,其中 li,ri 是 si 在 T 中的出现位置,因此需记录的是当前所有字符串的右端点最大值即 maxri。时间复杂度 O(n!2nnL)。
当然可以更优:用 log 级别的查找即 lower_bound
代替线性查找即可做到 O(nL+n!2nnlogL)。
2.最小值
一个显然的想法是舍弃所有被其它字串覆盖的子串,若相同则仅保留一个,因为要使答案最小让其被完全覆盖一定最优。那么剩下来的子串就一定满足若 li<lj 则一定有 ri<rj,这是很强的一个性质,并且结合最优化的限制,给予我们动态规划的思想:设 fi,S 表示前 i 位放置了集合 S 内的子串的最小值且第 i 位被覆盖,转移时枚举 p∈S 且 sp 与 T 在 i 处匹配。分两种情况讨论,一种是与已放置字符串有交集,另一种是不交,综合一下转移方程如下:
checkmin(fi,S,p∈Smin0≤j<iminfj,S\p+min(lenp,i−j))
当 j>i−lenp 时 i−j<lenp 故进行贡献为 lenp 的转移不会使答案变得更小(即更优),而当 j≤i−lenp 时 i−j>lenp 所以进行贡献为 i−j 的转移也不会影响答案,因此可以看做对于每个 j∈[0,i) 都进行 lenp 和 i−j 的转移。lenp 可以通过直接记录 fi,S 前缀最小值优化,而 i−j 的转移可以设 gi,S 表示 minj=0ifj,S−j 进行优化。再加上滚动数组,本部分时间复杂度 O(n2nL),空间复杂度更是仅有惊人的 O(2n)!
也许你会问:直接用求最小值的 DP 求最大值不就行了吗?非也,因为转移方程中 min(lenp,i−j) 的部分并没有变成 max,故此时 lenp 只能从 j≤i−lenp 转移,而 i−j 只能从 j>i−lenp 转移,所以需要加一个线段树维护区间修改与区间最值,很麻烦,不如直接贪心更方便。而且一道题目锻炼两种思维,岂不妙哉?
复杂度分析:本题的时间复杂度为 O(n!n2nlogL+n2nL),空间复杂度为 O(nL+2n)。很显然后者已经达到了理论下界。实现起来不算麻烦,而且效率非常优秀。
得吃代码:
#include <bits/stdc++.h>
using namespace std;
#define mem(x, v, s) memset(x, v, sizeof(x[0]) * (s))
template <class T1, class T2> void cmin(T1 &a, T2 b){a = a < b ? a : b;}
template <class T1, class T2> void cmax(T1 &a, T2 b){a = a > b ? a : b;}
const int N = 1e4 + 5;
char t[N], s[4][N];
int n, tL, len[4], nxt[4][N];
bool mat[4][N];
void KMP(char *s, int sL, int *nxt, bool *mat) {for(int i = 2; i <= sL; i++) {nxt[i] = nxt[i - 1];while(nxt[i] && s[nxt[i] + 1] != s[i]) nxt[i] = nxt[nxt[i]];if(s[nxt[i] + 1] == s[i]) nxt[i]++;}for(int i = 1, p = 0; i <= tL; i++) {while(p && s[p + 1] != t[i]) p = nxt[p];if(s[p + 1] == t[i]) p++;if(p == sL) mat[i] = 1, p = nxt[p];else mat[i] = 0;}
}
bool OverLap(char *t, char *s, int tL, int sL, int *nxt) {for(int i = 1, p = 0; i <= tL; i++) {while(p && s[p + 1] != t[i]) p = nxt[p];if(s[p + 1] == t[i]) p++;if(p == sL) return 1;}return 0;
}
int GetMax() {if(n == 1) return len[0];static int id[4], ans, pos[4][N], cnt[4]; ans = 0, mem(cnt, 0, 4);for(int i = 0; i < n; i++) id[i] = i;for(int i = 0; i < n; i++) for(int j = 1; j <= tL; j++) if(mat[i][j]) pos[i][cnt[i]++] = j - len[i];do {for(int S = 0; S < 1 << n - 1; S++) {int cur = -1, res = 0, rbound = 0;for(int bit = 0; bit < n; bit++) {int i = id[bit];if(!bit) {cur = pos[i][0], rbound = cur + len[i] - 1, res = len[i]; continue;}int p = -1, pr = id[bit - 1];if(S >> bit - 1 & 1) {int rlim = min(tL - len[i] + 1, cur + len[pr] - 1);int it = upper_bound(pos[i], pos[i] + cnt[i], rlim) - pos[i];if(it == 0 || pos[i][it - 1] < cur) break;p = pos[i][it - 1];}else {int it = lower_bound(pos[i], pos[i] + cnt[i], cur + len[pr]) - pos[i];if(it == cnt[i]) break;p = pos[i][it];}res += max(0, p + len[i] - 1 - max(rbound, p - 1));cmax(rbound, p + len[i] - 1), cur = p;}cmax(ans, res);}} while(next_permutation(id, id + n));return ans;
}
int GetMin() {if(n == 1) return len[0];static int ban[4], id[4], m; mem(ban, 0, 4), m = 0;for(int i = 0; i < n; i++) for(int j = 0; j < n; j++)if(strcmp(s[i] + 1, s[j] + 1)) ban[j] |= OverLap(s[i], s[j], len[i], len[j], nxt[j]);for(int i = 0; i < n; i++) for(int j = i + 1; j < n; j++) ban[j] |= !strcmp(s[i] + 1, s[j] + 1);for(int i = 0; i < n; i++) if(!ban[i]) id[m++] = i;static int f[2][16], g[2][16];mem(f, 0x3f, 2), mem(g, 0x3f, 2), f[0][0] = g[0][0] = 0;for(int i = 1, cur = 1, pr = 0; i <= tL; i++, swap(cur, pr)) {for(int j = 0; j < 1 << m; j++) {f[cur][j] = N;for(int k = 0; k < j; k++) {if(!(j >> k & 1)) continue;int S = j - (1 << k), p = id[k];if(mat[p][i]) cmin(f[cur][j], min(f[pr][S] + len[p], g[pr][S] + i));}g[cur][j] = min(g[pr][j], f[cur][j] - i), cmin(f[cur][j], f[pr][j]);}}return f[tL & 1][(1 << m) - 1];
}
void solve() {scanf("%s %d", t + 1, &n), tL = strlen(t + 1);for(int i = 0; i < n; i++) {scanf("%s", s[i] + 1);KMP(s[i], len[i] = strlen(s[i] + 1), nxt[i], mat[i]);}cout << GetMin() << " " << GetMax() << "\n";
}
int main(){int T; cin >> T;while(T--) solve();return 0;
}
感谢观看!!!