洛谷P4022 熟悉的文章
题意:给定一个串集合s,每次给定一个串t,询问一个最大的L,使得存在一种划分能把t划分成若干个子串, 其中好的子串总长不小于0.9|t|。好的子串定义为长度不小于L且是s中某一个串的子串。
解:发现这个L可以二分。如果一个L满足那么小一点的L也满足。考虑如何check。
可以求最长的总好的子串长度,然后看是否大于0.9。
这样就能想到DP了。设f[i]表示t[0:i]能划分出的最长好的子串。转移就是考虑第i是否是一个好的子串的结尾。如果是就枚举卡开头,否则就是f[i - 1]。
这个每个位置都有一个最长能匹配的长度mat[i],然后我们以i结尾的好的子串长度限制就是mat[i] ~ mid。
我们发现转移过来的两个限制,右边界显然是每次 + 1的,而左边界单调递增。具体来说,如果i结尾的最长匹配是[k, i],那么i - 1结尾的最长匹配不会比[k, i - 1]还短。
所以用广义SAM搞出来mat,然后二分 + 单调队列优化DP。
注意开头的时候f[0]是mat[0] >= mid。
1 #include <bits/stdc++.h> 2 3 const int N = 1000010; 4 const double eps = 1e-8; 5 6 int tr[N][2], fail[N], len[N], tot = 1; 7 int mat[N], f[N]; 8 int stk[N], top, head; 9 char str[N]; 10 11 inline int Max(const int &a, const int &b) { 12 return a > b ? a : b; 13 } 14 15 inline int split(int p, int f) { 16 int Q = tr[p][f], nQ = ++tot; 17 len[nQ] = len[p] + 1; 18 fail[nQ] = fail[Q]; 19 fail[Q] = nQ; 20 memcpy(tr[nQ], tr[Q], sizeof(tr[Q])); 21 while(tr[p][f] == Q) { 22 tr[p][f] = nQ; 23 p = fail[p]; 24 } 25 return nQ; 26 } 27 28 inline int insert(int f, int p) { 29 if(tr[p][f]) { 30 int Q = tr[p][f]; 31 if(len[Q] == len[p] + 1) { 32 return Q; 33 } 34 else { 35 return split(p, f); 36 } 37 } 38 int np = ++tot; 39 len[np] = len[p] + 1; 40 while(p && !tr[p][f]) { 41 tr[p][f] = np; 42 p = fail[p]; 43 } 44 if(!p) { 45 fail[np] = 1; 46 } 47 else { 48 int Q = tr[p][f]; 49 if(len[Q] == len[p] + 1) { 50 fail[np] = Q; 51 } 52 else { 53 fail[np] = split(p, f); 54 } 55 } 56 return np; 57 } 58 59 int large[N << 2]; 60 std::bitset<N * 4> tag; 61 62 inline void pushdown(int o) { 63 if(tag[o]) { 64 tag.set(o << 1); 65 tag.set(o << 1 | 1); 66 large[o << 1] = large[o << 1 | 1] = 0; 67 tag.reset(o); 68 } 69 return; 70 } 71 72 void change(int p, int v, int l, int r, int o) { 73 if(l == r) { 74 tag.reset(o); 75 large[o] = v; 76 return; 77 } 78 pushdown(o); 79 int mid = (l + r) >> 1; 80 if(p <= mid) change(p, v, l, mid, o << 1); 81 else change(p, v, mid + 1, r, o << 1 | 1); 82 large[o] = Max(large[o << 1], large[o << 1 | 1]); 83 return; 84 } 85 86 int getMax(int L, int R, int l, int r, int o) { 87 if(L <= l && r <= R) { 88 return large[o]; 89 } 90 int mid = (l + r) >> 1, ans = 0; 91 pushdown(o); 92 if(L <= mid) ans = getMax(L, R, l, mid, o << 1); 93 if(mid < R) ans = Max(ans, getMax(L, R, mid + 1, r, o << 1 | 1)); 94 return ans; 95 } 96 97 int main() { 98 int n, m; 99 scanf("%d%d", &n, &m); 100 for(int i = 1; i <= m; i++) { 101 scanf("%s", str); 102 int len = strlen(str), last = 1; 103 for(int j = 0; j < len; j++) { 104 last = insert(str[j] - '0', last); 105 } 106 memset(str, 0, len * sizeof(char)); 107 } 108 109 for(int A = 1; A <= n; A++) { 110 scanf("%s", str); 111 int Len = strlen(str); 112 113 int p = 1, lenth = 0; 114 for(int i = 0; i < Len; i++) { 115 int f = str[i] - '0'; 116 while(p && !tr[p][f]) { 117 p = fail[p]; 118 lenth = len[p]; 119 } 120 if(tr[p][f]) { 121 p = tr[p][f]; 122 lenth++; 123 } 124 else { 125 p = 1, lenth = 0; 126 } 127 mat[i] = lenth; 128 //printf("mat %d = %d \n", i, mat[i]); 129 } 130 131 int l = 0, r = Len; 132 while(l < r) { 133 int mid = (l + r + 1) >> 1; 134 135 f[0] = (mat[0] >= mid); /// ERROR : f[0] = mat[0] 136 stk[head = top = 1] = 0; 137 for(int i = 1; i < Len; i++) { 138 f[i] = f[i - 1]; 139 int L = i - mat[i], R = i - mid; 140 while(head <= top && f[R] - R >= f[stk[top]] - stk[top]) { 141 --top; 142 } 143 stk[++top] = R; 144 while(head < top && stk[head] < L) { 145 ++head; 146 } 147 if(L <= stk[head] && stk[head] <= R) { 148 f[i] = Max(f[i], f[stk[head]] + i - stk[head]); 149 } 150 } 151 //printf("mid = %d f = %d \n", mid, f[Len - 1]); 152 if(10 * f[Len - 1] >= 9 * Len) { 153 l = mid; 154 } 155 else { 156 r = mid - 1; 157 } 158 } 159 printf("%d\n", r); 160 memset(str, 0, Len * sizeof(char)); 161 } 162 163 return 0; 164 }
我非常傻,一开始写的是个线段树,没看出来单调性...