闲话 22.11.9
杂题
怎么字符串题单里面只有三个紫题啊
剩下的怎么全是黑的难度的啊
我是在备战 ZJOI 吗
只会贺
随做随写吧
给定一个字符串 \(S\) 和一个序列 \(W\),初始时它们都为空。你需要在线完成 \(n\) 次操作。每次操作在 \(S\) 后面添加一个字符 \(c\),在序列 \(W\) 后面添加一个数字 \(W_i\)。二者均加密。
定义一个子区间 \([L,R]\) 的可疑度为:
若子串 \([L,R]\) 和前缀 \([1,R-L+1]\) 相同,则其可疑度为 \(\min_{i=L}^{R} W_i\)。否则其可疑度为\(0\)。每次操作后,你都要求出当前的串的所有子区间的可疑度之和。
\(n\le 6\times 10^5,\ W< 2^{30}\)。
考虑每次更新答案只会加入右端点为新加入节点的区间。问题转化为动态维护 border 的权值和。
容易发现每次加入一个节点最多只会加入一个 border,因此总 border 数为 \(O(n)\) 的。因此考虑用 \(O(n \ f(n))\) 的方式均摊。
设当前加入的节点为 \(i\)。
我们需要维护 \(s[1\dots i-1]\) 的子串内所有 border,在加入元素时考虑这些 border 是否还能拓展。如果能拓展则这个 border 的权值和 \(w_i\) 取 \(\min\),答案相应更改。反之我们需要将这个 border 删掉。
考虑如何快速找到下一个需要被删掉的 border。
对每个节点维护一个数组 \(\text{fat}[]\) 表示该节点在 fail 树上深度最大且与当前节点对应前缀的下一个字母不同的节点。话比较绕嘴,但只需要明白这是为了快速找到不能拓展的前缀就行了。
每次从当前节点的 fail 开始跳 fat,如果可以删除就一直删除,删不了了就再跳一次 fat。最后跳到空串为止。
容易发现每个字符串都只会被遍历一次,因此这部分的复杂度到了 \(O(n)\)。
然后考虑能拓展的情况。我们维护一个单增栈,容易发现所有能对答案贡献的位置都在这个栈里面。
我们需要快速确定一个节点对应的答案是多少,这个可以在弹栈时用并查集将栈顶和当前节点合并。
最后是当前位置自己作为 border 和串首做贡献。
这时取栈首即可。
总时间复杂度 \(O(n\ \alpha(n))\)。实现的好的话可能是最优解?
会爆 long long
,请使用 __int128_t
或者 pair<long long, long long>
模拟。没必要写高精。
code
#include <bits/stdc++.h>
using namespace std;
#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)
const int N = 6e5 + 10, mask = (1 << 30) - 1;
const long long bse = 1e18;
// const int mod = 998244353;
int n, nxt[N], w[N], stk[N], top, fat[N];
char s[N];
__int128_t ans, now;
int fa[N], siz[N], sz[N];
int find(int u) { return u == fa[u] ? u : fa[u] = find(fa[u]); }
void merge(int & a, int & b) {
now -= 1ll * siz[b] * w[b];
now += 1ll * siz[b] * w[a];
if (sz[a] < sz[b]) swap(a, b);
fa[b] = a; sz[a] += sz[b];
w[a] = min(w[a], w[b]); siz[a] += siz[b];
}
signed main() {
cin.tie(0)->sync_with_stdio(false);
cin >> n >> s[1] >> w[1];
fa[1] = sz[1] = stk[top = 1] = 1;
ans += w[1]; printf("%lld\n", (long long)ans);
for (int i = 2, j = 0, k; i <= n; ++ i) {
cin >> s[i] >> w[i];
s[i] = (s[i] + ans - 'a') % 26 + 'a';
w[i] = (w[i] ^ (ans & mask));
stk[++top] = i, fa[i] = i, sz[i] = 1;
while (top > 1 and w[stk[top]] <= w[stk[top - 1]]) {
swap(stk[top], stk[top - 1]);
merge(stk[top - 1], stk[top]);
-- top;
}
fat[i - 1] = (s[i] == s[j + 1] ? fat[j] : j); // 找到后缀树上的祖先位置
while (j and s[j + 1] != s[i]) {
-- siz[find(i - j)];
now -= w[find(i - j)];
j = nxt[j];
}
k = fat[j];
if (s[j + 1] == s[i]) ++ j;
nxt[i] = j;
while (k) {
if (s[k + 1] == s[i]) {
k = fat[k];
} else {
-- siz[find(i - k)];
now -= w[find(i - k)];
k = nxt[k];
}
}
if (s[1] == s[i]) {
++ siz[find(i)];
now += w[find(i)];
}
ans += now + w[find(stk[1])];
if (ans > bse) printf("%lld%018lld\n", (long long)(ans / bse), (long long)(ans % bse));
else printf("%lld\n", (long long)ans);
}
}
给定一个字符串 \(S\),其长度为 \(N\)。
定义 \(\textrm{Per}(S)\) 为 \(S\) 的所有周期的集合。
多组数据,每次给一个 \(S\),要求你构造一个 \(01\) 串使得其 \(\textrm{Per}\) 集合与 \(S\) 相同。如果有多种答案,输出字典序最小的一种。
\(T \leq 20\),\(|S| \leq 2 \times 10^5\)。
切了,但不是很理解为什么
为啥我把得到的 \(qq'\) 再求一次 period 就会炸 删了就切了啊
似乎是子串 period 不是很一样 但是原串拼合后就一样了
考虑递归构造。
我们设 \(\text{solve}(l,r)\) 为字典序最小且对 \(s[l\dots r]\) 子串满足条件的 01 序列。然后可以分类讨论 \(\text{solve}(l,r)\) 是什么:
- 若 \(s[l\dots r]\) 子串的 period 为1,则为 \(r-l+1\) 个 \(0\) 。
- 若 \(s[l\dots r]\) 子串的 border 为1,则为 \(r-l\) 个 \(0\) 与 \(1\) 个 \(1\) 。
- 若 \(s[l\dots r]\) 子串的 period \(p\) 满足 \(2p \le r - l + 1\),则 \(s[l\dots r]\) 子串定可以表示为 \(s\dots ss'\) 的形式,其中 \(s'\) 为 \(s\) 的一个可空前缀。递归解决 \(ss'\)。设得到的答案为 \(tt'\),则 \(\text{solve}(l,r)\) 定可以表为 \(t\dots tt'\) 的形式,其中 \(t'\) 为 \(t\) 的一个可空前缀。
- 若 \(s[l\dots r]\) 子串的 period \(p\) 满足 \(2p > r - l + 1\),则 \(s[l\dots r]\) 子串定可以表示为 \(sas'\) 的形式,\(a\) 不可空。递归解决 \(s\)。设得到的答案为 \(t\),则 \(\text{solve}(l,r)\) 定可以表为 \(tbt\) 的形式,\(b\) 不可空。
若 \(b\) 为全 \(0\) 时满足条件则为全 \(0\) 即可,反之置 \(b\) 的最后一位为 \(1\) 定满足条件且最优。
到这里程序实现就没有问题了。难点在如何证明。
1.2. 显然,不证。
3.的情况需要一个引理。
\(\text{Weak Periodicity Lemma (WPL)}\)
对于一个字符串 \(s\),若其有长度为 \(p\) 和长度为 \(q\) 的周期,且 \(p+q \leq |s|\),则 \(s\) 有长度为 \(\gcd(p,q)\) 的周期。
证明:
不妨设 \(p < q\)。由于 \(p + q \le |s|\),则对于任意 \(i\),\(i - p\ge 0\) 和 \(i + q < s\) 定成立其中一个。因此可以向前跳 \(p\) 并向后跳 \(q\),二者在某种顺序下定成立。设 \(d = q - p\),则我们有 \(s[i + d] = s[i]\)。则 \(q - p\) 也是 \(s\) 的一个周期。
根据辗转相减法,原设成立。
然后可以有推论:
对于一个字符串 \(s\),若其最短周期为 \(l\) 且 \(2l \le |s|\),则任意长度大于 \(l + (|s| \bmod l)\) 的周期只能在 \(l\) 的周期首处取得。
证明考虑根据 \(\text{WPL}\) 进行反证法。
根据推论,我们的构造法保证了当 \(s\) 满足最短周期 \(l \nmid |s|\) 时的正确性。
当 \(s\) 的最短周期 \(l \mid |s|\) 时考虑 \(s\) 不会在这时成为原串的一个划分单元。
因此 3. 情况正确。
4.的情况一眼看上去挺显然的。那就不证了。
code
#include <bits/stdc++.h>
using namespace std;
#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)
const int N = 2e5 + 10;
// const int mod = 998244353;
int T, n;
char ch[N], tmp[N];
int nxt[N];
int period(int l, int r) {
int len = 0;
rep(i,l,r) tmp[++ len] = ch[i];
for (int i(2), j(0); i <= len; ++ i) {
while (j and tmp[j + 1] != tmp[i]) j = nxt[j];
if (tmp[j + 1] == tmp[i]) ++ j;
nxt[i] = j;
} return nxt[len];
}
int period(basic_string<char> ch) {
int len = 0;
rep(i,0,ch.length() - 1) tmp[++ len] = ch[i];
for (int i(2), j(0); i <= len; ++ i) {
while (j and tmp[j + 1] != tmp[i]) j = nxt[j];
if (tmp[j + 1] == tmp[i]) ++ j;
nxt[i] = j;
} return nxt[len];
}
basic_string<char> solve(int l, int r) {
int prd = r - l + 1 - period(l, r);
basic_string<char> now;
if (prd == 1) {
rep(i,l,r) now += '0';
} else if (prd == r - l + 1) {
rep(i,l,r - 1) now += '0';
now += '1';
} else if (prd * 2 <= (r - l + 1)) {
basic_string<char> qq_ = solve(r - prd - (r - l + 1) % prd + 1, r);
basic_string<char> tmp = qq_.substr(0, prd);
rep(i,1,floor(1. * (r - l + 1) / prd) - 1) now += tmp;
now += qq_;
} else {
basic_string<char> q = solve(l, r - prd);
now += q;
rep(i,1,2*prd - (r-l+1)) now += '0';
now += q;
if (r - l + 1 - period(now) != prd) {
now.clear();
now += q;
rep(i,2,2*prd - (r-l+1)) now += '0';
now += '1';
now += q;
}
}
return now;
}
signed main() {
cin.tie(0)->sync_with_stdio(false);
cin >> T;
while (T --) {
cin >> ch + 1;
cout << solve(1, strlen(ch + 1)) << endl;
}
}
给定长度为 \(n\) 的字符串 \(S\),现有一个空串 \(T\),每次可将 \(S\) 去掉一个 \(\text{border}\) 后接在 \(T\) 上。问 \(T\) 的长度可以是 \([n,w]\) 中的多少个数。
\(1\le n\le 5\times 10^5,1\le w\le 10^{18}\)。
我不是很理解我贺了个什么东西
他只跑了一次最短路,但是它把所有等差数列缩在了一个点里面
考虑正常做法(
我们发现,接在 \(T\) 上的 \(S\) 子串只能是 \(S\) 的 period。设 \(S\) 共有 \(k\) 个period,\(a_i\) 代表 \(S\) 的第 \(i\) 个 period,则我们需要的是 \(\sum_{i=1}^k a_ix_i\) 的取值个数。这启发我们使用同余最短路算法解决问题。
暴力跑 dij,点数 \(O(n)\) 边数 \(O(n^2)\) 因此有复杂度 \(O(n^2)\) 。谁在想带 log 的复杂度呢?
考虑优化。
有性质:所有长度 \(\ge\) 原串一半的 border 构成一个等差数列。
证明:
考虑 \(S\) 的最长 border \(A\),以及另一个 border \(B\),满足 \(|B| \ge |S|/2\)。
设 \(p = |S| -|A|\),\(q = |S| - |B|\)。由定义,\(p,q\) 为两个 period。由 \(\text{WPL}\) 可知 \(\gcd(p,q)\) 也是一个 period。因此 \(|S| - \gcd(p,q)\) 为一个 border。又因为 \(A\) 极长,因此这个 border 就是 \(A\)。因此 \(\gcd(p,q) = p\) 。因此 \(p \mid q\)。因为 \(\forall i\times q\) 也是 \(S\) 的周期,因此得证。
因此我们可以将原串的所有 border 分成两部分,一部分为等差数列,另一部分的最长长度小于 \(|S| / 2\)。递归可得最终 border 构成了 \(O(\log |S|)\) 个等差数列。
然后我们把每个等差数列分开处理。假设当前的等差数列形如 \(kx +b\),则我们取同余 \(b\) 跑最短路。然后连边考虑 \(\forall 0 \le y < x\),\(y\) 与 \((y + k) \bmod b\) 连边。这样形成了 \(\gcd(k, b)\) 个环。
从 \(b=0\) 的等差数列开始更新,每次选择当前所有点中 \(dis\) 最小点使用单调队列更新就可以做到转移。
然后是换模数。
假设原来的 \(dis\) 是 \(f\),当前的 \(dis\) 是 \(g\),模数为 \(m\),则我们有 \(g_i = \min_{f_j \bmod m= i} f_j\)。
然后转移即可。总时间复杂度 \(O(n\log n)\)。
↓ 我也不知道这份代码在写什么 晚上好好研究一下
code
#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;
#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)
using ll = long long; using ull = unsigned long long;
const int N = 2e6 + 10;
// const int mod = 998244353;
int T, n, nxt[N], beg[N], val[N], num[N], top;
ll m, ans, dis[N];
bool vis[N];
char s[N];
__gnu_pbds :: priority_queue <pair<ll, int> > que;
signed main() {
cin.tie(0)->sync_with_stdio(false); cout.tie(0);
cin >> T;
nxt[0] = -1;
while (T --) {
cin >> n >> m >> s + 1; m -= n;
top = ans = 0;
for (int i(2), j(0); i <= n; ++ i) {
while (j and s[j + 1] != s[i]) j = nxt[j];
if (s[j + 1] == s[i]) ++ j;
nxt[i] = j;
}
for (int i = nxt[n]; i >= 0; i = nxt[i]) {
if (top and (num[top] == 1 or n - i == beg[top] + val[top] * num[top]))
val[top] = (n - i - beg[top]) / (num[top] ++);
else beg[++ top] = n - i, num[top] = 1;
}
rep(i,0,beg[1] - 1) dis[i] = 1e18 + 11e8, vis[i] = false;
que.push( {dis[0] = 0, 0} );
while (que.size()) {
int u = que.top().second; que.pop();
if (vis[u]) continue;
vis[u] = 1;
rep(i,1,top) for (int j(beg[i]), k(0), d(val[i]), r(beg[i] + val[i] * (num[i] - 1)); j <= r; j += val[i], d += val[i]) {
k = (u + j) % beg[1];
if (dis[k] > dis[u] + j)
dis[k] = dis[u] + j, que.push( {-dis[k], k} );
if (dis[(u + d) % beg[1]] <= dis[u] + d) break;
}
}
rep(i,0,beg[1] - 1) if (dis[i] <= m) ans += (m - dis[i]) / beg[1] + 1;
cout << ans << endl;
}
}
给一棵树,每条边上有一个字符,求有多少对 \((x,y)(x<y)\),满足 \(x\) 到 \(y\) 路径上的边上的字符按顺序组成的字符串为回文串。
点数 \(\le 5\times 10^4\),字符 \(\in \{0,1\}\)。
点分。
首先以当前点分中心为根,将根到当前联通块内每个节点路径上的 \(01\) 串插入自动机。
考虑答案一定形如 \(\text{ST|S}\),其中 \(\text{T}\) 是一个回文串,\(|\) 是重心所在的位置。设串首的节点为 \(x\),串尾节点为 \(y\)。
首先在自动机的 fail 树上进行 dfs,把每个点作为 \(y\) 的贡献求出来。
然后 \(T\) 部分是好判的,哈希即可。
然后由于最终的回文子串构成了 \(O(\log |S|)\) 个等差数列,根号分治一下就能暴力了。
总时间复杂度 \(O(n\sqrt n)\)。
根号平衡的 \(B = 10\) 时跑得飞快,已经是 loj 上最优解了
code
#include <bits/stdc++.h>
using namespace std;
using ll = long long; using ull = unsigned long long;
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 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)
const int N = 5e4 + 10;
const int B = 10;
// const int mod = 469762049;
// const int mod = 998244353;
const int mod = 1004535809;
// const int mod = 1e9 + 7;
// const int mod = 1e9 + 9;
// const int mod = 1e9 + 3579, bse = 131;
template <typename T1, typename T2> T1 add(T1 a, T2 b) { return (a += b) >= mod ? a - mod : a; }
template <typename T1, typename ...Args> T1 add(T1 a, Args ... b) { return add(a, add(b...)); }
int n, pw[N], t1, t2, t3;
ll ans, res;
vector<pair<int,int> > e[N];
bool vis[N];
int ch[N][2], s[N], fail[N], dep[N], mlc;
vector<int> g[N];
vector<tuple<int,int,int> > q[N];
int New() {
ch[mlc][0] = ch[mlc][1] = s[mlc] = fail[mlc] = 0;
q[mlc].clear(), g[mlc].clear();
return mlc ++;
}
void build(int u, int fa, int & id) {
if (!id and fa) id = New();
++ s[id];
for (auto [v, w] : e[u]) if (!vis[v] and fa != v)
build(v, u, ch[id][w]);
}
queue <int> que;
void build() {
rep(i,0,1) if (ch[0][i]) que.push(ch[0][i]);
while (que.size()) {
int u = que.front(); que.pop();
g[fail[u]].emplace_back(u);
rep(i,0,1) {
if (!ch[u][i]) ch[u][i] = ch[fail[u]][i];
else fail[ch[u][i]] = ch[fail[u]][i], que.push(ch[u][i]);
}
}
}
vector <pair<int,int> > b[N];
void dfs1(int u, int hshl, int hshr, int ls) {
if (u and hshl == hshr) {
if (b[u].size() and dep[u] - ls == b[u].back().first) ++ b[u].back().second;
else b[u].emplace_back(dep[u] - ls, 1);
ls = dep[u];
} rep(i,0,1) if (ch[u][i]) {
b[ch[u][i]] = b[u];
dep[ch[u][i]] = dep[u] + 1;
dfs1(ch[u][i], add(hshl, hshl, i), add(hshr, i * pw[dep[u]]), ls);
}
}
int id[N], stk[N], top, cnt[N], c[B + 5][B + 5];
void dfs2(int u) {
id[top] = u, stk[top] = dep[u];
++ top;
cnt[dep[u]] = s[u];
res += 1ll * s[u] * (s[u] - 1) >> 1;
int len = 0;
for (auto [l, t] : b[u]) {
if (l >= B) {
while (t --) res += 1ll * s[u] * cnt[dep[u] - (len += l)];
} else {
int lp = dep[u] - len - l * t, rp = dep[u] - len - l;
if (stk[0] < lp) q[id[lower_bound(stk, stk + top, lp) - stk - 1]].emplace_back(-s[u], l, rp % l);
q[id[upper_bound(stk, stk + top, rp) - stk - 1]].emplace_back(s[u], l, rp % l);
len += l * t;
}
}
for (int i = 1; i < B; ++ i) c[i][dep[u] % i] += s[u];
for (auto v : g[u]) dfs2(v);
for (auto [cont, i, j] : q[u]) res += 1ll * cont * c[i][j];
for (int i = 1; i < B; ++ i) c[i][dep[u] % i] -= s[u];
-- top; cnt[dep[u]] = 0;
}
ll calc(int u, int w) {
res = mlc = 0; int tmp = New();
if (w == -1) build(u, 0, tmp);
else ch[tmp][w] = New(), build(u, 0, ch[tmp][w]);
dfs1(0, 0, 0, 0);
build();
dfs2(0);
return res;
}
int sum, rt, siz[N], f[N];
void get_rt(int u, int fa) {
siz[u] = 1; f[u] = 0;
for (auto [v, w] : e[u]) if (!vis[v] and v != fa) {
get_rt(v, u);
siz[u] += siz[v];
f[u] = max(f[u], siz[v]);
} f[u] = max(f[u], sum - siz[u]);
if (f[rt] > f[u]) rt = u;
}
void solve(int u) {
rt = 0; get_rt(u, 0); vis[rt] = 1;
ans += calc(rt, -1);
for (auto [v, w] : e[rt]) if (!vis[v]) {
ans -= calc(v, w);
sum = siz[v];
solve(v);
}
}
signed main() {
get(n); pw[0] = 1;
rep(i,1,n) pw[i] = add(pw[i-1], pw[i-1]);
rep(i,2,n) get(t1, t2, t3), e[t1].emplace_back(t2, t3), e[t2].emplace_back(t1, t3);
sum = n, f[0] = 1e9;
solve(1);
cout << ans << endl;
}
以下是博客签名,与正文无关。
请按如下方式引用此页:
本文作者 joke3579,原文链接:https://www.cnblogs.com/joke3579/p/chitchat221109.html。
遵循 CC BY-NC-SA 4.0 协议。
请读者尽量不要在评论区发布与博客内文完全无关的评论,视情况可能删除。