CF1286E Fedya the Potter Strikes Back

\(\Large\texttt{CF1286E Fedya the Potter Strikes Back }\)

期望理论复杂度是 \(\mathcal O(n\alpha(n))\),理论上是吊打了表算,但是好像实际并不优于一些 \(\mathcal O(n\log n)\) 的提交。

思路

对于我们需要动态维护的这个答案,先做个归类,将所有有贡献的区间 \([L, R]\),令其为点 \(R\) 的贡献,发现很难每次直接对点 \(i\) 算出其贡献,考虑继承 \(i - 1\) 的贡献。

为了方便理解,这里引入后缀树的概念。

对于点 \(i - 1\) 和 \(i\),它们在后缀树上的深度一定满足 \(dep_{i} \le dep_{i - 1} + 1\),也就是说每次添加一个字符最多只会增加一个 border,且其长度为 \(1\)。

那么我们考虑如何继承 \(i - 1\) 的贡献到 \(i\),对于点 \(i\) 在后缀树上到根节点上的所有点都一定是点 \(i - 1\) 在后缀树上的到根节点上的所有点继承而来(除了长为 \(1\) 的border), 也就是说,我们将点 \(i\) 有贡献的区间 \([L, i]\) 中的所有左端点归为集合 \(S_i\),那么 \(S_i \in S_{i - 1} \cup \{i\}\),因此我们只要暴力删除所有不满足的 \(L\) 并在 \(S_{i - 1}\) 之中,其它直接继承即可,删除总数最大为 \(\mathcal O(n)\)。

我们用单调栈维护后缀递增的 \(w_i\),因为只有这些元素是有贡献的,并在此过程中将当前所有 \(L \in S_i\) 插入其中属于其贡献的位置 \(w_i\),对于插入新的元素 \(w_i\),若需要合并删去栈顶元素,那么用并查集将其指向 \(i\) 即可,也就是将原本贡献在栈顶的元素移到 \(i\)。

对于删除操作和其它题解差不多,维护一个点在后缀树上第一个后继字符和自己后继字符不一样的祖先,暴力跳并删除即可。

代码

#include <bits/stdc++.h>
using namespace std;

#define PB push_back
// #define int long long
#define LL long long
#define siz(a) ((int)((a).size()))
#define rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define per(i, a, b) for (int i = (a); i >= (b); --i)
const int N = 6e5;
const int M = 100000;
const int mod = 1e9 + 7;
const int inf = 1e9;

int a, nxt[N + 5], w[N + 5], fa[N + 5], top, siz[N + 5], sk[N + 5], lst[N + 5], sz[N + 5];
__int128 ans, tmp;
char s[N + 5];

int f(int n) {
	return fa[n] == n ? n : fa[n] = f(fa[n]);
}

void merge(int &p1, int &p2) {
	tmp -= 1ll * w[p2] * siz[p2];
	tmp += 1ll * w[p1] * siz[p2];
	if(sz[p1] < sz[p2]) swap(p1, p2);
	w[p1] = min(w[p1], w[p2]);
	fa[p2] = p1;
	siz[p1] += siz[p2];
	sz[p1] += sz[p2];
}
void out(__int128 x){
	static int buf[255], len;
	if(!x) return cout << "0\n", void();
	while(x) buf[++len] = x % 10, x /= 10;
	while(len) cout << (char) (buf[len--] + '0');
	cout << "\n";
}

signed main() {
	// freopen("in1.in", "r", stdin);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> a;
	int j = 0, mx = (1ll << 30) - 1;
	rep(i, 1, a) {
		cin >> s[i] >> w[i];
		w[i] = (w[i] ^ (ans & mx));
		s[i] = (ans + s[i] - 'a') % 26 + 'a';
		sk[++top] = i;
		fa[i] = i;
		sz[i] = 1;
		while(top > 1 && w[sk[top]] <= w[sk[top - 1]]) {
			swap(sk[top], sk[top - 1]);
			merge(sk[top - 1], sk[top]);
			--top;
		}
		if(i > 1) {
			lst[i - 1] = ((s[i] == s[j + 1]) ? lst[j] : j);
			for(; j && s[j + 1] != s[i]; j = nxt[j]) {
				--siz[f(i - j)];
				tmp -= w[f(i - j)];
			}
			for(int k = lst[j]; k; ) {
				if(s[k + 1] == s[i]) {
					k = lst[k];
					continue;
				}
				--siz[f(i - k)];
				tmp -= w[f(i - k)];
				k = nxt[k];
			}
			if(s[j + 1] == s[i]) ++j;
			nxt[i] = j;
			if(s[1] == s[i]) {
				++siz[f(i)];
				tmp += w[f(i)];
			}
		}
		ans += tmp + w[f(sk[1])];
		out(ans);
	}
	return 0;
}
上一篇:最小矩形覆盖


下一篇:0019-leetcode算法实现之删除链表倒数第n个节点-remove-nth-node-from-end-of-list-python&golang实现