闲话 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。每个匹配都是子树加,可以用树状数组轻易维护。
说起来简单但是调着很麻烦?
杂题
设随机变量 \(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]\) 的方式:
我们记 \(\max X\) 的取值上界为 \(R = \max\left\{R_i\right\}\)。重写答案为
这样只需要在 \([0,R]\) 的范围内考察 \(F\)。
我们发现,\(\max X \le x\),当且仅当 \(\forall X_i \le x\)。因此 \(\max X\) 的 CDF 应当是各个 \(X_i\) 的 CDF \(f_i\) 的乘积:
而 \(f_i(x)\) 是易表出的:
\(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]\) 考虑的积分是
由于 \(F\) 为多项式,直接做即可。
计算结果乘入 \((n+1)! \times \prod_{i=1}^n (R_i - L_i)\) 即为最终答案。
总时间复杂度为 \(O(n^2)\)。
以下是博客签名,与正文无关。
请按如下方式引用此页:
本文作者 joke3579,原文链接:https://www.cnblogs.com/joke3579/p/chitchat221223.html。
遵循 CC BY-NC-SA 4.0 协议。
请读者尽量不要在评论区发布与博客内文完全无关的评论,视情况可能删除。