BZOJ 4560 [JLOI2016]字符串覆盖
2018-01-08 14:57
218 查看
这是一道如果没想清楚就不要乱打的题目,
否则就像我一样~~
题目描述
字符串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,子串长充<=1000
思考了接近一个小时,我想到了一种很难打的dp
设dp[i][S][j]表示到了原串的第i个位置,已经选了集合的状态为S,右端点最远覆盖到j。
这样就可以分情况讨论转移
其中now代表以i位置为左端点的串是哪个,r代表当前串的右端点
1.r’ < i时,dp[i][S | (1 << now)][r] = min{dp[j][s][r’] + (r - i + 1)}(1 <= j <= i-1)
2.i<= r’ < r时,dp[i][S | (1 << now)][r] = min{dp[j][s][r’] + r-r’}(1 <= j <= i-1)
3.r’ > i时,dp[i][S | (1 << now)][r’] = min{dp[j][s][r’]}(1 <= j <= i-1)
于是我们可以发现可以以每一个状态建立一棵线段树方便转移
至于有哪些串以当前位置为左端点可以kmp预处理,再用vector保存
但是我们发现这样会有一个严重的问题,就是当出现第3种情况时
也就是r’ > i时,转移到的状态最右端点变成了r’,而如果是这样,就意味着
我们需要枚举r’,这样复杂度突然就多了一个n,是无法承受的
事实上,这种情况,也就是区间互相包含的情况,需要特殊处理
我们在开始还要建立一棵线段树,预处理出每一个区间里所有包含的区间的二进制表示,也就是上面的now变成了多个串的二进制表示,这样最小值就可以直接转移了
但是最大值又会有问题,因为如果(S&now)不为零,那么就无法转移,结果会变大。所以只有在S&now不为零的时候转移,但又要考虑所有状态,所以还要枚举now的子集转移,复杂度变成了3^n *n *log2(n),常数巨大,还要卡一下常数。
如果你已经懂了思想,就自己写吧。
但如果有兴趣,也不妨看看我8.2k的代码
否则就像我一样~~
题目描述
字符串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,子串长充<=1000
思考了接近一个小时,我想到了一种很难打的dp
设dp[i][S][j]表示到了原串的第i个位置,已经选了集合的状态为S,右端点最远覆盖到j。
这样就可以分情况讨论转移
其中now代表以i位置为左端点的串是哪个,r代表当前串的右端点
1.r’ < i时,dp[i][S | (1 << now)][r] = min{dp[j][s][r’] + (r - i + 1)}(1 <= j <= i-1)
2.i<= r’ < r时,dp[i][S | (1 << now)][r] = min{dp[j][s][r’] + r-r’}(1 <= j <= i-1)
3.r’ > i时,dp[i][S | (1 << now)][r’] = min{dp[j][s][r’]}(1 <= j <= i-1)
于是我们可以发现可以以每一个状态建立一棵线段树方便转移
至于有哪些串以当前位置为左端点可以kmp预处理,再用vector保存
但是我们发现这样会有一个严重的问题,就是当出现第3种情况时
也就是r’ > i时,转移到的状态最右端点变成了r’,而如果是这样,就意味着
我们需要枚举r’,这样复杂度突然就多了一个n,是无法承受的
事实上,这种情况,也就是区间互相包含的情况,需要特殊处理
我们在开始还要建立一棵线段树,预处理出每一个区间里所有包含的区间的二进制表示,也就是上面的now变成了多个串的二进制表示,这样最小值就可以直接转移了
但是最大值又会有问题,因为如果(S&now)不为零,那么就无法转移,结果会变大。所以只有在S&now不为零的时候转移,但又要考虑所有状态,所以还要枚举now的子集转移,复杂度变成了3^n *n *log2(n),常数巨大,还要卡一下常数。
如果你已经懂了思想,就自己写吧。
但如果有兴趣,也不妨看看我8.2k的代码
#include<bits/stdc++.h> #define ll long long #define mm(a, b) memset(a, b, sizeof(a)) #define inf 999999999 #define For(i, a, b) for(int i = (a);i <= (b); ++i) #define rep(i, a, b) for(int i = (a);i >= (b); --i) #define gnn cerr << "GNN睡着了" << endl; using namespace std; int read(){ int sum = 0, fg = 1; char c = getchar(); while(c < '0' || c > '9'){if(c == '-')fg = -1;c = getchar();} while(c >='0' && c <='9')sum = (sum << 1) + (sum << 3) + c-'0', c = getchar(); return sum * fg; } const int maxn = 50010; void file(){ #ifndef ONLINE_JUDGE freopen("Thunder.in", "r", stdin); freopen("Thunder.out", "w", stdout); #endif } char a[maxn], ss[10][maxn]; int n, Next[maxn], len[maxn]; vector<int> pos[maxn], ans[maxn]; void Get(){ scanf("%s", a+1); n = read(); For(i, 1, n){ scanf("%s", ss[i] + 1); len[i] = strlen(ss[i] + 1); } len[0] = strlen(a+1); } void Next_Done(int h){ int k = 0; Next[1] = 0; For(i, 2, len[h]){ while(k && ss[h][k+1] != ss[h][i])k = Next[k]; if(ss[h][k+1] == ss[h][i]) ++k; else k = 0; Next[i] = k; } } void Solve(int h){ int j = 0; For(i, 1, len[0]){ while(j && ss[h][j+1] != a[i])j = Next[j]; if(ss[h][j+1] == a[i]){ ++ j; if(j == len[h]){ pos[i - len[h] + 1].push_back(h); j = Next[j]; } } else j = 0; } } void pre_work_kmp(){ For(i, 1, n){ For(j, 0, len[0] + 1) Next[j] = 0; Next_Done(i); Solve(i); } } int fugai[maxn]; int NOWANS; void insert_fugai(int h,int l,int r,int loc,int now){ if(l == r){ fugai[h] |= (1 << now-1); return; } int mid = l+r >> 1; if(loc <= mid) insert_fugai(h << 1, l, mid, loc, now); else insert_fugai(h << 1 | 1, mid+1, r, loc, now); fugai[h] = (fugai[h << 1] | fugai[h << 1 | 1]); } int query_fugai(int h,int l,int r,int s,int e){ if(l == s && r == e) return fugai[h]; int mid = l+r >> 1; if(e <= mid) return query_fugai(h << 1, l, mid, s, e); else if(s > mid) return query_fugai(h << 1 | 1, mid+1, r, s, e); else return (query_fugai(h << 1, l, mid, s, mid) | query_fugai(h << 1 | 1, mid+1, r, mid+1, e)); } void print(int h){ For(i, 1, n) if(h & (1 << i-1)) printf("1"); else printf("0"); puts(""); } void pre_work_fugai(){ rep(i, len[0], 1){ for(int j = 0;j < pos[i].size(); ++j){ int v = pos[i][j]; int R = i + len[v] - 1; insert_fugai(1, 1, len[0], R, v); } for(int j = 0;j < pos[i].size(); ++j){ int v = pos[i][j]; int R = i + len[v] - 1; int Now = (query_fugai(1, 1, len[0], i, R) | (1 << v-1)); ans[i].push_back(Now); } } } int tree_one[(1 << 4) + 10][maxn << 2], tree_two[(1 << 4) + 10][maxn << 2], dp[(1 << 4) + 10][maxn]; void pre_work(){ For(i, 0, len[0]) ans[i].clear(), pos[i].clear(); mm(fugai, 0); pre_work_kmp(); pre_work_fugai(); } void insert_one(int h,int l,int r,int S,int loc,int val){ if(l == r){ tree_one[S][h] = min(tree_one[S][h], val); return; } int mid = l+r >> 1; if(loc <= mid) insert_one(h << 1, l, mid, S, loc, val); else insert_one(h << 1 | 1, mid+1, r, S, loc, val); tree_one[S][h] = min(tree_one[S][h << 1], tree_one[S][h << 1 | 1]); } void query_one(int h,int l,int r,int s,int e,int S){ if(tree_one[S][h] >= NOWANS) return; if(l == s && r == e){ NOWANS = tree_one[S][h]; return ; } int mid = l+r >> 1; if(e <= mid) query_one(h << 1, l, mid, s, e, S); else if(s > mid) query_one(h << 1 | 1, mid+1, r, s, e, S); else return query_one(h << 1, l, mid, s, mid, S), query_one(h << 1 | 1, mid+1, r, mid+1, e, S); } void insert_two(int h,int l,int r,int S,int loc,int val){ if(l == r){ tree_two[S][h] = min(tree_two[S][h], val - l); return; } int mid = l+r >> 1; if(loc <= mid) insert_two(h << 1, l, mid, S, loc, val); else insert_two(h << 1 | 1, mid+1, r, S, loc, val); tree_two[S][h] = min(tree_two[S][h << 1], tree_two[S][h << 1 | 1]); } void query_two(int h,int l,int r,int s,int e,int S){ if(tree_two[S][h] >= NOWANS) return; if(l == s && r == e){ NOWANS = tree_two[S][h]; return ; } int mid = l+r >> 1; if(e <= mid) query_two(h << 1, l, mid, s, e, S); else if(s > mid) query_two(h << 1 | 1, mid+1, r, s, e, S); else return query_two(h << 1, l, mid, s, mid, S), query_two(h << 1 | 1, mid+1, r, mid+1, e, S); } int ls[(1 << 4) + 10][maxn << 2]; void solve_min(){ int tmp = (1 << n) - 1; for(int i = 0;i <= tmp; ++i) for(int j = 0;j <= len[0]; ++j) dp[i][j] = inf; for(int i = 0;i <= tmp; ++i) for(int j = 0;j <= len[0] * 4; ++j) tree_one[i][j] = tree_two[i][j] = inf; dp[0][0] = 0; insert_one(1, 0, len[0], 0, 0, 0); insert_two(1, 0, len[0], 0, 0, 0); int Min = inf; For(i, 1, len[0]){ For(j, 0, tmp - 1){ for(int k = 0;k < pos[i].size(); ++k){ int v = ans[i][k]; int R = i + len[pos[i][k]] - 1; ls[j][k] = dp[j | v][R]; if((j | v) == j) continue; int nowval = inf; NOWANS = inf; query_one(1, 0, len[0], 0, i-1, j); nowval = min(nowval, NOWANS + len[pos[i][k]]); NOWANS = inf; query_two(1, 0, len[0], i, R, j); nowval = min(nowval, NOWANS + R); dp[j | v][R] = min(dp[j | v][R], nowval); if(dp[j | v][R] < ls[j][k] && dp[j | v][R] < inf / 2){ insert_one(1, 0, len[0], (j | v), R, dp[j | v][R]); insert_two(1, 0, len[0], (j | v), R, dp[j | v][R]); } } } Min = min(Min, dp[tmp][i]); } printf("%d ", Min); } void _insert_one(int h,int l,int r,int S,int loc,int val){ if(l == r){ tree_one[S][h] = max(tree_one[S][h], val); return; } int mid = l+r >> 1; if(loc <= mid) _insert_one(h << 1, l, mid, S, loc, val); else _insert_one(h << 1 | 1, mid+1, r, S, loc, val); tree_one[S][h] = max(tree_one[S][h << 1], tree_one[S][h << 1 | 1]); } void _query_one(int h,int l,int r,int s,int e,int S){ if(tree_one[S][h] < NOWANS) return; if(l == s && r == e){ NOWANS = tree_one[S][h]; return; } int mid = l+r >> 1; if(e <= mid) _query_one(h << 1, l, mid, s, e, S); else if(s > mid) _query_one(h << 1 | 1, mid+1, r, s, e, S); else return _query_one(h << 1, l, mid, s, mid, S), _query_one(h << 1 | 1, mid+1, r, mid+1, e, S); } void _insert_two(int h,int l,int r,int S,int loc,int val){ if(l == r){ tree_two[S][h] = max(tree_two[S][h], val - l); return; } int mid = l+r >> 1; if(loc <= mid) _insert_two(h << 1, l, mid, S, loc, val); else _insert_two(h << 1 | 1, mid+1, r, S, loc, val); tree_two[S][h] = max(tree_two[S][h << 1], tree_two[S][h << 1 | 1]); } void _query_two(int h,int l,int r,int s,int e,int S){ if(tree_two[S][h] < NOWANS) return; if(l == s && r == e){ NOWANS = tree_two[S][h]; return; } int mid = l+r >> 1; if(e <= mid) _query_two(h << 1, l, mid, s, e, S); else if(s > mid) _query_two(h << 1 | 1, mid+1, r, s, e, S); else return _query_two(h << 1, l, mid, s, mid, S), _query_two(h << 1 | 1, mid+1, r, mid+1, e, S); } void solve_max(){ int tmp = (1 << n) - 1; for(int i = 0;i <= tmp; ++i) for(int j = 0;j <= len[0]; ++j) dp[i][j] = -inf; for(int i = 0;i <= tmp; ++i) for(int j = 0;j <= len[0] * 4; ++j) tree_one[i][j] = tree_two[i][j] = -inf; dp[0][0] = 0; _insert_one(1, 0, len[0], 0, 0, 0); _insert_two(1, 0, len[0], 0, 0, 0); int Max = -inf; For(i, 1, len[0]){ For(j, 0, tmp){ for(int k = 0;k < pos[i].size(); ++k){ int v = ans[i][k]; int R = i + len[pos[i][k]] - 1; if(j & (1 << pos[i][k]-1)) continue; int nowval = 0; NOWANS = -inf; _query_one(1, 0, len[0], 0, i-1, j); nowval = max(nowval, NOWANS + len[pos[i][k]]); NOWANS = -inf; _query_two(1, 0, len[0], i, R, j); nowval = max(nowval, NOWANS + R); int now = (v ^ (1 << pos[i][k]-1)); int T = (j | (1 << pos[i][k]-1)); int Just = dp[T][R]; dp[T][R] = max(dp[T][R], nowval); if(dp[T][R] > Just){ _insert_one(1, 0, len[0], T, R, dp[T][R]); _insert_two(1, 0, len[0], T, R, dp[T][R]); } for(int o = now;o ;o = ((o-1)&now)){ Just = dp[T | o][R]; dp[T | o][R] = max(dp[T | o][R], nowval); if(dp[T | o][R] > Just){ _insert_one(1, 0, len[0], (T | o), R, dp[T | o][R]); _insert_two(1, 0, len[0], (T | o), R, dp[T | o][R]); } } } } Max = max(Max, dp[tmp][i]); } printf("%d\n", Max); } int main(){ file(); int _ = read(); while(_ --){ Get(); pre_work(); solve_min(); solve_max(); } return 0; }
相关文章推荐
- 【BZOJ4560】[JLoi2016]字符串覆盖 KMP+状压DP
- 【BZOJ】4560: [JLoi2016]字符串覆盖
- [BZOJ4560][JLOI2016]字符串覆盖(贪心+DP)
- BZOJ 4560 [JLoi2016]字符串覆盖
- bzoj 4565: [Haoi2016]字符合并 (字符串dp)
- BZOJ4002 [JLOI2015]有意义的字符串 【数学 + 矩乘】
- [JLoi 2016] bzoj4559 成绩比较 [容斥原理]
- 【BZOJ4002】【JLOI2015】有意义的字符串 推公式+矩阵乘法
- 【bzoj4561】【JLOI2016】【圆的异或并】【扫描线+set】
- 【BZOJ 4598】【SDOI 2016 Round2 Day1 T3】模式字符串
- bzoj 4002 [JLOI2015]有意义的字符串 数学
- 【BZOJ4598】[Sdoi2016]模式字符串 树分治+hash
- 【BZOJ 4557】【JLOI 2016】侦查守卫
- [BZOJ4558][JLoi2016]方(数学相关+容斥原理)
- ●BZOJ 4559 [JLoi2016]成绩比较
- BZOJ4560 JLOI2016字符串覆盖(kmp+贪心+状压dp+单调队列)
- bzoj 4557: [JLoi2016]侦察守卫 树归
- bzoj 4002: [JLOI2015]有意义的字符串(特征根法+矩阵快速幂)
- bzoj4558[JLoi2016]方 容斥+count
- bzoj千题计划270:bzoj4559: [JLoi2016]成绩比较(拉格朗日插值)