[字典序] [dp] CF938F Erasing Substrings
posted on 2024-03-13 13:36:43 | under | source
首先删去的长度不相交,否则一定可以拆成不相交的。
然后有暴力,\(f_{i,j}\) 表示前缀 \(i\) 删去集合为 \(j\) 时,最小字典序(存一个串)。没啥好说的,就注意 \(j\) 恰好是当前删去元素个数。复杂度 \(O(n^4)\)。
考虑优化。看到字典序,就能想到一定要让其前缀尽可能小。于是对于所有长度相同的答案串,只保留字典序最小的即可。
现在 \(f\) 只可能是字典序最小或者空串。干脆用 bool 表示,\(=1\) 表示取到最小。
然后理所应当地更改枚举顺序,最外层枚举答案串长度。
我们先找到当前第 \(p\) 位最小可取的元素 \(mi\),然后转移。直接枚举子集复杂度太高,可以拆成两部分。
具体地,先转移第 \(i\) 位为 \(mi\) 的 \(f_{i,j}\),条件是 \(f_{i-1,i-j}=1\) 且 \(S_i=mi\)。再考虑删去元素,即 \(f_{i,j}\to f_{i+2^k,i+2^k-j}\)。
复杂度 \(O(n^2\log n)\)。
此题的启示:对于字典序 \(\rm dp\),可考虑贪心地使每一位最小,从而缩小状态、优化转移。
代码
注意代码实现中,不仅需保证每一位取得最小,还需使其有解。
如何判断呢?设当前删去元素集合为 \(A\),总集为 \(S\),则 \(A\le S\) 时,必有 \(A\) 和 \(S-A\) 的与值为 \(0\),即它们不会删去同一种 \(2^k\)。
换句话说,满足 \(A\le S\) 就有解,写代码时加入这句话即可。
码量极其友好。
#include<bits/stdc++.h>
using namespace std;
const int N = 5e3 + 5;
int n, m, M;
char c[N];
bool f[N][N];
signed main(){
scanf("%s", c + 1), n = strlen(c + 1), m = log2(n), M = (1 << m) - 1;
for(int i = 0; i <= n; ++i) f[i][i] = true;
for(int i = 1; i <= n - M; ++i){
char mi = 'z';
for(int j = i - 1; j - i + 1 <= M; ++j) if(f[j][j - i + 1]) mi = min(mi, c[j + 1]);
for(int j = i; j - i <= M; ++j) f[j][j - i] = f[j - 1][j - i] & (c[j] == mi);
for(int j = i; j - i <= M; ++j)
for(int k = 0; k < m && j + (1 << k) - i <= M; ++k)
if(!(((j - i) >> k) & 1)) f[j + (1 << k)][j + (1 << k) - i] |= f[j][j - i];
putchar(mi);
}
return 0;
}

浙公网安备 33010602011771号