闲话 22.12.23

闲话

?今天发之前忘记写闲话了(((

今天给我随了一首《二次死亡》出来
被狠狠地刀了
但歌是真挺好听的
推荐听一听哦!

字符串测试 1

为了纪念(记忆中)第一场码到想吐的模拟赛 写一下题解罢
这好像是我第一次写这种题解?不懂不懂

码农啊!

回文子串

我们发现 \(k\) 比较的小,这使得我们能设计一些复杂度与 \(k\) 相关的算法。

考虑将查询分成两部分的贡献。我们预处理以每个点为左端点且长度不超过 \(k\) 的回文数量 \(d(i)\),求解 \([l,r]\) 段询问时就可以先加入 \([l, r - k + 1]\) 段的 \(d\)。发现剩余的回文串都是 \([r - k + 2, r]\) 段内长度达不到 \(k\) 的字符串,这一部分没法预处理,但是长度是 \(O(k)\),因此可以使用 manacher 计算。

然后考虑带修。

发现 \(i\in [l, r - k + 1]\) 段的 \(d(i)\) 肯定更新成 \(k\)。结合查询,我们可以采用区间覆盖区间求和的线段树维护信息。然后 \([l - k, l + k]\) 段和 \([r - k + 2, r + k]\) 段都是不确定的,这一部分可以使用 manacher 计算,随后线段树上单点修改。
对于字符串区间覆盖可以采用分块维护。常数小点。

code
#include <bits/stdc++.h>
using namespace std; using pii = pair<int,int>; using vi = vector<int>; using vp = vector<pii>; using ll = long long; 
using ull = unsigned long long; using db = double; using ld = long double; using lll = __int128_t;
mt19937 rnd(chrono::steady_clock::now().time_since_epoch().count());
template <typename T> T rand(T l, T r) { return uniform_int_distribution<T>(l, r)(rnd); }
template <typename T> void get(T & x) {
	x = 0; char ch = getchar(); bool f = false; while (ch < '0' or ch > '9') f = f or ch == '-', ch = getchar();
	while ('0' <= ch and ch <= '9') x = (x << 1) + (x << 3) + ch - '0', ch = getchar(); f && (x = -x); 
} template <typename T, typename ... Args> void get(T & a, Args & ... b) { get(a); get(b...); }
#define iot ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
template <typename T1, typename T2> T1 min(T1 a, T2 b) { return a < b ? a : b; }
template <typename T1, typename T2> T1 max(T1 a, T2 b) { return a > b ? a : b; }
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define ufile(x) 
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
#define Aster(i, s) for (int i = head[s], v; i; i = e[i].next)
#define all(s) s.begin(), s.end()
#define eb emplace_back
#define pb pop_back
#define em emplace
const int N = 4e5 + 10, bse = 131;
int n, q, k, typ, l, r, ans[N];
char c;

string str; int m;
struct Mana {
	int p[N], siz, lp[N], rp[N], bel[N];
	char ch[N], lzy[N];
	void init() {
		n = strlen(ch + 1), siz = sqrt(n);
		rep(i,1,n) {
			bel[i] = (i - 1) / siz + 1;
			if (bel[i] != bel[i - 1]) rp[bel[i - 1]] = i - 1, lp[bel[i]] = i;
		} rp[bel[n]] = n;
	}

	void upd(int l, int r, char c) {
		int bll = bel[l], blr = bel[r];
		if (bll == blr) {
			if (lzy[bll]) rep(i,lp[bll],rp[bll]) ch[i] = lzy[bll];
			lzy[bll] = 0;
			rep(i,l,r) ch[i] = c; 
		} else {
			rep(i,bll + 1, blr - 1) lzy[i] = c;
			if (lzy[bll]) rep(i,lp[bll],rp[bll]) ch[i] = lzy[bll];
			lzy[bll] = 0;
			rep(i,l,rp[bll]) ch[i] = c;
			if (lzy[blr]) rep(i,lp[blr],rp[blr]) ch[i] = lzy[blr];
			lzy[blr] = 0;
			rep(i,lp[blr],r) ch[i] = c;
		} 
	}

	char get(int pos) {
		if (lzy[bel[pos]] != 0) return lzy[bel[pos]];
		return ch[pos];
	}

	void calc(int l = 1, int r = n) {
		str.clear();
		l = max(1, l); r = min(n, r);
		str += "(#";
		rep(i,l,r) str += get(i), str += '#';
		str += ")"; 
		m = str.size() - 1;
		rep(i,1,m) ans[i] = p[i] = 0;
		int mid = 0; r = 0;
		rep(i,1,m) {
			if (r > i) p[i] = min(p[mid * 2 - i], r - i);
			else p[i] = 1;
			while (i - p[i] > 1 and i + p[i] < m and str[i - p[i]] == str[i + p[i]])
				 ++ p[i];
			if (i + p[i] > r) r = i + p[i], mid = i;
			++ ans[i - min(k, p[i]) + 1];
			-- ans[i + 1];
		} rep(i,1,m) ans[i] += ans[i - 1]; 
	}
} Mr;

struct SegmentCitrus {
	#define siz(p) seg[p].siz
	#define sum(p) seg[p].sum
	#define lzy(p) seg[p].lzy
	#define ls (p << 1)
	#define rs (p << 1 | 1)
	
	struct node {
		int siz, sum, lzy;
	} seg[N << 2];

	void ps_d(int p) {
		if (lzy(p) == -1) return;
		sum(ls) = siz(ls) * lzy(p), lzy(ls) = lzy(p);
		sum(rs) = siz(rs) * lzy(p), lzy(rs) = lzy(p);
		lzy(p) = -1;
	}

	void ps_p(int p) {
		sum(p) = sum(ls) + sum(rs);
	}

	void build(int p = 1, int l = 1, int r = n) {
		siz(p) = r - l + 1, lzy(p) = -1, sum(p) = 0;
		if (l == r) {
			sum(p) = min(k, ans[l << 1]);
			return ;
		} int mid = l + r >> 1;
		build(ls, l, mid);
		build(rs, mid + 1, r);
		ps_p(p);
	}

	void upd(int p, int l, int r, int L, int R, int v) {
		if (L > R) return;
		if (L <= l and r <= R) {
			sum(p) = v * siz(p), lzy(p) = v;
			return;
		} int mid = l + r >> 1; ps_d(p);
		if (L <= mid) upd(ls, l, mid, L, R, v);
		if (mid < R) upd(rs, mid + 1, r, L, R, v);
		ps_p(p);
	}

	int query(int p, int l, int r, int L, int R) {
		if (L > R) return 0;
		if (L <= l and r <= R) return sum(p);
		int mid = l + r >> 1, ret = 0; ps_d(p);
		if (L <= mid) ret += query(ls, l, mid, L, R);
		if (mid < R) ret += query(rs, mid + 1, r, L, R);
		return ret;
	}
} Tr;

signed main() {
	cin >> Mr.ch + 1 >> k >> q; 
	Mr.init(), Mr.calc();
	Tr.build();
	while (q --) {
		cin >> typ >> l >> r;
		if (typ == 1) {
			cin >> c;
			Mr.upd(l, r, c), Mr.calc(l - k + 1, l + k);
			Tr.upd(1, 1, n, l, r - k + 1, k);
			for (int i = 2, p = max(0, l - k) + 1; i < m and p < l; i += 2, ++ p) 
				Tr.upd(1, 1, n, p, p, min(k, ans[i]));
			Mr.calc(r - k + 2, r + k);
			for (int i = 2, p = max(0, r - k + 1) + 1; i < m and p <= r; i += 2, p ++) 
				Tr.upd(1, 1, n, p, p, min(k, ans[i]));
		} else {
			int ret = Tr.query(1, 1, n, l, r - k);
			Mr.calc(max(l, r - k + 1), r);
			for (int i = 2; i < m; i += 2) ret += ans[i];
			cout << ret << '\n';
		}
	}
}



recollection

给的是一棵 Trie。套路地先建出 GSA,提取后缀链接树然后再考虑维护信息。

发现信息的表示比较显然了。两个点对应字符串的 \(\text{LCP}\) 长度是点在 Trie 树上的 \(\text{LCA}\) 深度。\(\text{LCS}\) 长度是点在后缀链接树上的 \(\text{LCA}\) 深度。

经典转化,处理一棵树的信息,在另一棵树上枚举 \(\text{LCA}\) 后合并子树信息。处理啥呢?咋合并呢?
考虑经典结论,一棵树上给定一系列点,选择两个点使得 \(\text{LCA}\) 深度最大,则我们可以先处理出 \(\text{dfn}\) 序,按照 \(\text{dfn}\) 序排序,选择相邻点的 \(\text{LCA}\) 能取到最大深度的 \(\text{LCA}\)

然后我们就可以整一个 set 维护 \(\text{dfn}\) 序,然后在一棵树上标记另一棵树的对应节点并 dfs,过程中合并子树信息,求 \(\text{LCA}\) 更新答案。启发式 + set 能做到 \(O(n\log ^2 n)\)。采用 \(O(1)\text{ LCA}\) 与 vEB 树达到应用启发式的(可能的)复杂度下界 \(O(n\log n\log \log n)\)
还可以使用线段树合并代替 set,得到复杂度 \(O(n\log n)\)。-By Delov

code
#include <bits/stdc++.h>
using namespace std; using pii = pair<int,int>; using vi = vector<int>; using vp = vector<pii>; using ll = long long; 
using ull = unsigned long long; using db = double; using ld = long double; using lll = __int128_t;
mt19937 rnd(chrono::steady_clock::now().time_since_epoch().count());
template <typename T> T rand(T l, T r) { return uniform_int_distribution<T>(l, r)(rnd); }
template <typename T> void get(T & x) {
	x = 0; char ch = getchar(); bool f = false; while (ch < '0' or ch > '9') f = f or ch == '-', ch = getchar();
	while ('0' <= ch and ch <= '9') x = (x << 1) + (x << 3) + ch - '0', ch = getchar(); f && (x = -x); 
} template <typename T, typename ... Args> void get(T & a, Args & ... b) { get(a); get(b...); }
#define iot ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
template <typename T1, typename T2> T1 min(T1 a, T2 b) { return a < b ? a : b; }
template <typename T1, typename T2> T1 max(T1 a, T2 b) { return a > b ? a : b; }
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define ufile(x) 
#define rep(i,s,t) for (int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (int i = (s), i##_ = (t) - 1; i > i##_; -- i)
#define Aster(i, s) for (int i = head[s], v; i; i = e[i].next)
#define all(s) s.begin(), s.end()
#define eb emplace_back
#define pb pop_back
#define em emplace
const int N = 5e5 + 10;
const int inf = 0x3f3f3f3f;
const ll infll = 0x3f3f3f3f3f3f3f3fll;
int n, t1, t2, t3, ans;
vp g[N];
vi g1[N], g2[N];

int dfn[N], idfn[N], stp, dep[N], fa[N], top[N], siz[N], son[N];
void dfs1(int u, int fat) {
	siz[u] = 1, fa[u] = fat, dep[u] = dep[fat] + 1;
	for (auto v : g1[u]) {
		dfs1(v, u);
		siz[u] += siz[v];
		if (siz[v] > siz[son[u]]) son[u] = v; 
	}
}

void dfs2(int u, int ctop) {
	dfn[u] = ++ stp, idfn[stp] = u, top[u] = ctop;
	if (son[u]) dfs2(son[u], ctop);
	for (auto v : g1[u]) if (v != son[u])
		dfs2(v, v);
}

int lca(int u, int v) {
	u = idfn[u], v = idfn[v];
	while (top[u] != top[v]) {
		if (dep[top[u]] < dep[top[v]]) swap(u, v);
		u = fa[top[u]];
	} return dep[u] < dep[v] ? u : v;
}

int pos[N];
struct GSA {
	int mlc = 1, link[N], len[N];
	unordered_map<int,int> son[N];

	int extend(int c, int lst) {
		int now = ++ mlc, p = lst;
		len[now] = len[lst] + 1;
		while (p and !son[p][c]) 
			son[p][c] = now, p = link[p];
		if (!p) link[now] = 1;
		else {
			int q = son[p][c];
			if (len[q] == len[p] + 1) link[now] = q;
			else {
				int kage = ++ mlc;
				len[kage] = len[p] + 1, link[kage] = link[q];
				link[q] = link[now] = kage;
				son[kage] = son[q];
				while (p and son[p][c] == q) 
					son[p][c] = kage, p = link[p];
			}
		} return now;
	}

	int ret = 0;
	queue<tuple<int,int,int> > que;
	void build() {
		for (auto[v, w] : g[1]) 
			que.emplace(v, w, 1);
		while (que.size()) {
			auto[u, c, lst] = que.front(); que.pop();
			lst = extend(c, lst);
			auto itr = st[lst].insert(dfn[u]).first;
			if (itr != st[lst].begin()) {
				ret = max(ret, dep[lca(*prev(itr), *itr)] + len[lst]);
			}
			if (itr != prev(st[lst].end())) {
				ret = max(ret, dep[lca(*itr, *next(itr))] + len[lst]);
			}
			for (auto[v, w] : g[u])
				que.emplace(v, w, lst);
		}
	}

	int buk[N], id[N];
	set<int> st[N];
	int calc() {
		rep(i,1,mlc) buk[len[i]] ++;
		rep(i,1,mlc) buk[i] += buk[i - 1];
		pre(i,mlc,1) id[buk[len[i]] --] = i;
		pre(i,mlc,2) {
			int u = id[i], fat = link[u];
			if (st[fat].size() < st[u].size()) swap(st[fat], st[u]);
			for (auto val : st[u]) {
				auto itr = st[fat].insert(val).first;
				if (itr != st[fat].begin()) {
					ret = max(ret, dep[lca(*prev(itr), *itr)] + len[fat]);
				}
				if (itr != prev(st[fat].end())) {
					ret = max(ret, dep[lca(*itr, *next(itr))] + len[fat]);
				}
			} 
		} return ret;
	}
} S;

signed main() {
	get(n);
	rep(i,2,n) get(t1, t2), g[t1].eb(i, t2), g1[t1].eb(i);
	dep[0] = -1; dfs1(1, 0), dfs2(1, 1);
	S.build();
	rep(i,2,S.mlc) g2[S.link[i]].eb(i);
	cout << S.calc() << '\n';
}



回忆树

拆询问为两部分。

第一部分是跨过 \(\text{LCA}\) 的,由于长度不大于 \(2|S|\)\(\text{LCA}\) 往下分别延伸 \(|S|\))因此可以 kmp 暴力匹配。
第二部分是满足可减性的。套路考虑离线差分。

我们对询问串的正反串建出两个 \(AC\) 自动机(ACAM),随后在上面匹配给定的 Trie。每个匹配都是子树加,可以用树状数组轻易维护。

说起来简单但是调着很麻烦?

Submission.

杂题

Random Max

设随机变量 \(X_1, X_2, \dots, X_n\) 分别在区间 \([L_1, R_1], [L_2, R_2], \dots, [L_n, R_n]\) 上均匀分布,求 \(\max(X_1, X_2, \dots, X_n)\) 的期望值。

\(\max X\) 为代表 \(\max(X_1, X_2, \dots, X_n)\) 的随机变量。同时设 \(F(x) = \Pr[\max X \le x]\)\(\max X\) 的累积分布函数(CDF)。令 \(\max X\) 的概率密度函数(PDF)为 \(P(x)\)。由变量的取值非负,且 PDF 的积分为 CDF,我们可以导出使用 CDF 表示 \(E[\max X]\) 的方式:

\[\begin{aligned} & E[\max X] \\ = \ & \int_{0}^{\infty} yP(y) \text d y \\ = \ & \int_{0}^{\infty} \int_{0}^{y}P(y) \text dx \text d y \\ = \ & \int_{0}^{\infty} \int_{x}^{\infty}P(y) \text d y\text d x \\ = \ & \int_{0}^{\infty}\left( 1 - \int_{0}^{x}P(y) \text d y \right) \text d x \\ = \ & \int_{0}^{\infty}1 - F(x) \text d x \end{aligned}\]

我们记 \(\max X\) 的取值上界为 \(R = \max\left\{R_i\right\}\)。重写答案为

\[R - \int_{0}^{R} F(x) \text d x \]

这样只需要在 \([0,R]\) 的范围内考察 \(F\)

我们发现,\(\max X \le x\),当且仅当 \(\forall X_i \le x\)。因此 \(\max X\) 的 CDF 应当是各个 \(X_i\) 的 CDF \(f_i\) 的乘积:

\[F(x) = \Pr [\max X \le x] = \prod_{i=1}^n \Pr [X_i \le x] = \prod_{i=1}^n f_i(x) \]

\(f_i(x)\) 是易表出的:

\[f_i(x) = \left\{ \begin{aligned} & 0 && x\le L_i \\ & \frac{x - L_i}{R_i - L_i} && L_i \le x \le R_i \\ & 1 && x \ge R_i \\ \end{aligned} \right .\]

\(L_i \le x \le R_i\) 段的解析式可由 CDF 的定义导出。

注意到我们可以通过将 \([0, R]\)\(L_i, R_i\) 为间隔分为 \(O(n)\) 个段,从大到小进行处理。如果扫到一个 \(L_i\),则无需接着进行,因为这其中定有一个元素为 \(0\),之后的 \(F(x)\) 定为 \(0\)

注意到加入一段等于是给当前的 \(F(x)\) 乘入了一个度数为 \(1\) 的多项式,且 \(F(x)\) 初始时 \(=1\),因此 \(F\) 在任何时刻也是多项式。我们在移动段时只需要乘入 \(\frac{x - L_i}{R_i - L_i}\) 即可。

需要注意的是,我们对每段 \([l, r]\) 考虑的积分是

\[\int_{l}^{r}1 - F(x) \text d x = (r - l) - \int_{l}^{r}F(x) \text d x \]

由于 \(F\) 为多项式,直接做即可。

计算结果乘入 \((n+1)! \times \prod_{i=1}^n (R_i - L_i)\) 即为最终答案。

总时间复杂度为 \(O(n^2)\)

Submission.

posted @ 2022-12-23 20:06  joke3579  阅读(69)  评论(1编辑  收藏  举报