ABC240Ex Sequence of Substrings
模拟赛题。
有意思的。
注意到奇怪的 \(n\leq 2.5 \times 10^4\)。
首先可以写出暴力 dp。设 \(f_{i,j}\) 表示考虑长度为 \(i\) 的前缀,最后一次选了 \(s[i - j + 1, i]\) 的贡献。
转移:
\[f_{i, j} = \max_{1\leq x \leq i - j,s[x - y + 1, y] < s[i - j + 1, i]}\{f_{x, y}\} + 1
\]
这是一个二维偏序的形式,但是状态数是 \(O(n^2)\) 的,直接转移复杂度为 \(O(n^2\log n)\)。
稍微推一推,发现最优决策中一定有一种情况满足,一个子串的长度如果为 \(len\),那么其后继长度最多为 \(len + 1\),那么所有的答案串长度都无法超过 \(2\sqrt n\),所以有用的状态数只有 \(O(n\sqrt n)\)。
于是就做完了,把所有有用状态全部压进一棵 trie 里面,一边遍历一边做二维偏序,复杂度 \(O(n\sqrt n \log n)\)。
代码非常好写。
code
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2.5e4 + 10;
const int maxs = 2 * sqrt(2.5e4) + 10;
int lowbit(int x) {return x & (-x);}
int tr[maxn * maxs][2], cnt = 1;
vector<int> loc[maxn * maxs];
int mxl;
int ans;
int n;
class Fenwick {
public:
int tr[maxn];
void Update(int x, int val) {
while(x <= n) {
tr[x] = max(val, tr[x]);
x += lowbit(x);
}
}
int Query(int x) {
int res = 0;
while(x) {
res = max(res, tr[x]);
x -= lowbit(x);
}
return res;
}
}fw;
string s;
void Insert(int x) {
int p = 1;
int lim = min(n, x + mxl);
for(int i = x; i <= lim; i++) {
int &nxt = tr[p][s[i - 1] - '0'];
if(!nxt) nxt = ++cnt;
p = nxt;
loc[p].emplace_back(i);
}
}
vector<pair<int, int>> tmp;
void Dfs(int u, int len) {
tmp.clear();
for(int i : loc[u]) {
int f = fw.Query(i - len) + 1;
ans = max(ans, f);
tmp.emplace_back(i, f);
}
for(auto cur : tmp) {
fw.Update(cur.first, cur.second);
}
for(int i = 0; i < 2; i++) if(tr[u][i]) Dfs(tr[u][i], len + 1);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> s;
mxl = 2 * sqrt(n);
for(int i = 1; i <= n; i++) {
Insert(i);
}
Dfs(1, 0);
cout << ans;
}