11 月杂记
我心已死,万念俱灰……草泥马的这个世界……
Invitation? Sounds great......
CF
CF2156D *1900
想到的:考虑从低位向高位枚举二进制位进行计数,我们可以判断出来这一位究竟有没有少数,然后注意到这样子是:\(n+\frac{n}{2}+\frac{n}{4}+...+\frac{n}{2^{\log_2 n}}\le 2\times n\)。弱智题。
CF2146E *2300
一开始看错题目了。。。
想到的:将题目中的 \(b_i>\operatorname{mex}(b)\) 改成 \(i>\operatorname{mex}(b)\) 的话可以这么考虑。正确性未知。即观察到对于每一个 \(r\),左端点取 \(l\) 的话 \(\operatorname{mex}(l, r)\) 是随着 \(l\) 增大而单调不增的。设枚举到区间 \([l, r]\) 时的 \(\operatorname{mex}\) 为 \(x\),则对于 \([l, x]\) 这段区间是一定没有贡献的,因此可以将 \(l\) 指针一直增加,同时更新 \(x\) 的值,直到 \(x<l\)。可以用双指针维护 \(l\) 与 \(r\),然后用一个桶加线段树上二分即可维护出 \(\operatorname{mex}\) 了。
没想到的:可以对于每一个 \(r\) 都维护出当 \(\operatorname{mex}\) 取 \(i,i\in [0, n + 1]\) 时的答案。对于新加入一个数 \(x\),则对于所有的 \(\operatorname{mex}<x\) 的答案都会增加 \(1\) 的答案,同时,对于 \(\operatorname{mex}=x\) 的答案会归为 \(0\)。直接用线段树维护这个东西即可,答案即为全局最大值。
CF2157E *?
想到的:如果一种数有可能被执行操作,那么该数最后只会留下比它小的需要加一的数与该种数的第一个出现的数的位置最小值。
然后从小到大枚举每一种数,观察到如果 \(x-1\) 的数需要操作,那么 \(x\) 如果出现过,就必然也需要操作
所以用一个 set 去维护当前枚举到的数的下标,然后每次取出第一个下标就好了。
因为每一个下标只会被放进去和取出来一次,总时间复杂度 \(O(n\log n)\)。
666,读错题了,每次如果满足后就去个 \(\min\) 即可。
CF2021C2 *1900
想到的:观察到如果去重以后的序列如果和 a 相等,那么就是可以的
现在考虑加上修改操作后怎么做
这真的是 *1900 吗
观察是每次修改,如果被改动的数是第一个出现的,那么就要判断它与下一次出现的同样的数中有没有第一次出现的
数,然后如果原来是正确,此时就是错误;那怎么判断此时错误的是否会变成正确的呢?哦,只需要知道上一个位置的数前面有多少个第一次出现的数,
然后就知道这个数在原数中的下标了
现在会判断这个数变成第一次出现的数了,如何判断这个数原来是第一次出现的,现在不是了呢?如何判断这个数后面的一串数呢?
hash?线段树维护区间 hash?
考虑线段树怎么动态地维护 hash,简单的,维护一个 sz 和 hash 值即可
???被卡 hash 了?6,被卡 base 了
CF1450D *1800
和栋栋 duel 到的题目。
想到的:先考虑能否套路地用单调栈把每一个数的左右第一个比它小的数的位置处理出来。发现似乎这样没什么用。然后观察样例,发现 \(1\) 的位置只出现在了第一个以及最后一个,所以考虑每一个数能放的位置。注意到如果可以形成长度为 \(x\) 的排列,则 \(1\sim x-1\) 必然只能按顺序出现在数列的两侧。双指针维护排列的边界即可。注意要特判 \(x=n\) 的情况。
CF2159A *1700
交互题。
想到的:考虑观察一些性质出来。感觉也是套路的,先找出一对数的下标,然后再一次找出其它的数。注意到最好找的便是最大的那一对数。这需要耗费 \(n\) 次询问。注意到一个东西,那就是如果往序列里新加入一个数,且该次询问的答案与之前不同,则新加入的那一个数便是新的答案。所以考虑先搞出来一个每个数都只出现了一次的序列,然后去询问剩下的数,同理把一开始搞出来的序列也询问了就搞定了。最坏情况 \(4n\)。操。哦,第一次找序列的时候也记录一下答案就行了。
CF2164D *1800
想到的:注意到每次操作等同于把原串中的一部分给它往前移一位,贪心地考虑,对于每一个需要改变的字符 \(s_i\),选择 \(s_j=s_i\) 且有 \(\min_{j\le i}\{i-j\}\)。设每一个要改变的字符的 \(s_j\) 为 \(lst_i\),则如果出现 \(j\le i\) 且 \(s_i<s_j\) 则必然无解。否则最小次数便是 \(\max\{i-lst_i\}\)。接着应该就是模拟加输出了。样例没过?注意到如果 \(lst_i=-1\) 且存在 \(j>i,lst_j<i\) 那么 \(lst_i=lst_{i-1}\) 或 \(lst_i=lst_j\)。如果不能满足则无解。同时,不难发现如果 \(lst_i=lst_j\) 则 \(j\) 必然是离 \(i\) 最近的满足 \(lst_j<i\) 的位置。有点小假,观察一下发现只要相对位置不相交即可,所以可以二分出每一个字母从哪一个字母移动而来。然后因为要次数最少,着怎么搞?从后往前二分贪心应该就行了,每次都选择最近的。过了……唉,还是每次思考的都不够全面,但怎样才能把所有情况都考虑到呢?
AT
闲来没事补 AT。
abc
428f *2000
想到的:发现一个性质,无论怎么操作各个区间一定满足最初始的包含关系。然后答案可以转换为所有区间减去右边界小于等于 \(x\) 的区间,再减去左边界大于等于 \(x\) 的区间。对于第一种操作,就相当与把所有需改变的左边界按区间终点进行对称了,所以直接开两棵线段树维护区间的左右端点即可。
没想到的:上面的做法太复杂了,容易发现答案一定是一段后缀区间,也就是说不存在左边界大于等于 \(x\) 的情况。
430f *1600
想到的:考虑拆买一个数的贡献,一眼瞪出来一个结论,即每一个数能放到位置一定是一个连续的区间。考虑如何计算每一个数的区间。对于 L,该数能到达的左边界为它后面的数中 L 的个数加一,右边界为 \(n\) 减前面连续的 L 的个数加一。有猫腻。大体思路没有错,只不过细节很多,左边界和右边界的判定直接加减就行了。
LG
P14379 \(\color{blue}\text{蓝}\)
想到的:
- 如果 \(f(x, x + 1)\) 需要改变高度,那么包含这段区间的区间就都会有贡献了。
- 观察到只有 \(1\) 和其它非 \(1\) 的数在一起才会产生贡献。
- 所以考虑维护 \(1\) 与其它数的位置。
- 假了,注意到如果一个非 \(1\) 的数两边都出现了 \(1\),并且该数大于 \(2\),则这显然是没有贡献的。
- 考虑思考如果两边都有 \(1\) 的情况,中间有 \(2\) 才会有贡献。
- 总结一下,要么就是只有一边有 \(1\),要么就是两边都有 \(1\) 且一边有 \(2\)。
- 然后现在正难则反,维护没有贡献的区间个数。
- 拿这个上线段树即可。
- 考虑需要维护什么,即左右的最长连续非 \(1\) 子串长度、左右的连续 \(1\) 子串长度、左右的连续 \(1\) 和非 \(2\) 的子串长度、区间答案。
- 不对,因为合并需要 \(1\) 的数量,所以应该维护非 \(2\) 子串中 \(1\) 的数量。
没想到的:整合一下发现,
- 如果没有 \(1\) 则 \(f(l, r) = 0\)。
- 如果存在 \(a_l = a_r = 1\) 且 \((l, r)\) 中不包含 \(2\),则 \(f(l, r) = 0\)。
P14479 \(\color{blue}\text{蓝}\)
补一道月赛的 ad-hoc。
想到的:会 \(O(qn^2)\) 的 dp 做法,但似乎不是很能优化?观察到拼接次数是 \(\log\) 级别的,所以能否以此优化?另一个 observation 为单次拼接的贡献的最大值不会超过现在的子串的长度,由于拼接具有连续性,所以假设现在 \([1, i]\) 的数处理完了,只需要判断 \((i, 2\times i]\) 中有多少个数会有贡献即可,然后对于 \([2\times i, n]\) 的 dp 值都去一个 \(\max\),线段树维护即可做到 \(O(qn\log n)\)。离线去 \(\log\) 吗?有点意思……但咋搞啊?等会,如何判断有多少个数会有贡献?不管了,\(O(qn^2)\) 草过去。
P2414 \(\color{purple}\text{紫}\)
我勒个数据结构爆超串串题。独立切紫祭……
想到的:既然是做 ACAM 做到的题,那就必然和 ACAM 有点关系。注意到在 ACAM 上如果一个串串的后缀等于另一个串串,那么必然可以从前一个串串不断跳 fail 跳到后一个串串。所以依照 fail 指针建一棵树,然后判断 \(y\) 是否存在一个后缀等于 \(x\) 就只需要判断 \(y\) 在不在 \(x\) 的子树里。回到题目,由于 \(y\) 串中每一个位置都有可能相等的后缀的结束位置,所以需要查询在 ACAM 中根到 \(y\) 串的结束位置的点中有多少个点在 fail 树中处于 \(x\) 的子树中。于是可以树链剖分 ACAM 中的 trie,然后用主席树去维护 trie 中的节点在 fail 树中的 dfs 序,接着便做完了。
代码是优美呢?还是史山呢?调了我两小时……
::::success[Code]
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ld = long double;
using ull = unsigned long long;
using i128 = __int128;
using PII = pair<int, int>;
using PLL = pair<ll, ll>;
constexpr ll inf = (1ll << 62);
constexpr int N = 1e5 + 10;
string s;
int n, tot, tot1, cnt;
struct Trie {
int son[26], fail;
} trie[N];
vector<vector<int>> G(N);
vector<int> dfn1(N), sz1(N), dfn(N), sz(N), son(N), top(N), root(N), d(N), fa(N, -1), rnk(N);
int cur;
struct Tree {
int l, r, val;
} tree[N << 6];
int get(char c) {
return c - 'a';
}
void build() {
queue<int> q;
for (int i = 0; i < 26; i++) {
if (trie[0].son[i]) {
trie[trie[0].son[i]].fail = 0;
q.push(trie[0].son[i]);
}
}
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = 0; i < 26; i++) {
if (!trie[u].son[i]) trie[u].son[i] = trie[trie[u].fail].son[i];
else {
trie[trie[u].son[i]].fail = trie[trie[u].fail].son[i];
q.push(trie[u].son[i]);
}
}
}
for (int i = 1; i <= tot; i++) {
G[i].push_back(trie[i].fail);
G[trie[i].fail].push_back(i);
}
}
void dfs(int u, int fa) {
dfn1[u] = ++cnt;
sz1[u] = 1;
for (auto v : G[u]) {
if (v == fa) continue;
dfs(v, u);
sz1[u] += sz1[v];
}
}
void dfs1(int u) {
sz[u] = 1;
son[u] = -1;
for (int i = 0; i < 26; i++) {
if (!trie[u].son[i]) continue;
dfs1(trie[u].son[i]);
sz[u] += sz[trie[u].son[i]];
if (son[u] == -1 || sz[son[u]] < sz[trie[u].son[i]]) {
son[u] = trie[u].son[i];
}
}
}
void dfs2(int u, int t) {
dfn[u] = ++tot1;
rnk[tot1] = u;
top[u] = t;
if (son[u] != -1) dfs2(son[u], t);
for (int i = 0; i < 26; i++) {
if (!trie[u].son[i] || son[u] == trie[u].son[i]) continue;
dfs2(trie[u].son[i], trie[u].son[i]);
}
}
void pushup(int p) {
tree[p].val = tree[tree[p].l].val + tree[tree[p].r].val;
}
int update(int l, int r, int x, int k, int p) {
tree[++cur] = tree[p];
p = cur;
if (l == r) {
tree[p].val = k;
return p;
}
int mid = l + r >> 1;
if (x <= mid) tree[p].l = update(l, mid, x, k, tree[p].l);
else tree[p].r = update(mid + 1, r, x, k, tree[p].r);
pushup(p);
return p;
}
int query(int l, int r, int x, int y, int p1, int p2) {
if (x <= l && r <= y) {
return tree[p2].val - tree[p1].val;
}
int mid = l + r >> 1, ans = 0;
if (x <= mid) ans += query(l, mid, x, y, tree[p1].l, tree[p2].l);
if (mid < y) ans += query(mid + 1, r, x, y, tree[p1].r, tree[p2].r);
return ans;
}
int ask(int u, int v) {
int ans = 0;
while (v >= 0) {
ans += query(1, tot + 1, dfn1[u], dfn1[u] + sz1[u] - 1, root[dfn[top[v]] - 1], root[dfn[v]]);
v = fa[top[v]];
}
return ans;
}
void solve() {
cin >> s;
vector<char> cur;
int num = 0, p = 0;
for (int i = 0; i < s.size(); i++) {
if (s[i] == 'P') {
d[++num] = p;
} else if (s[i] == 'B') {
p = fa[p];
cur.pop_back();
} else {
if (!trie[p].son[get(s[i])]) trie[p].son[get(s[i])] = ++tot;
cur.push_back(s[i]);
fa[tot] = p;
p = trie[p].son[get(s[i])];
}
}
dfs1(0);
dfs2(0, 0);
build();
dfs(0, -1);
for (int i = 1; i <= tot + 1; i++) {
root[i] = update(1, tot + 1, dfn1[rnk[i]], 1, root[i - 1]);
}
cin >> n;
while (n--) {
int x, y;
cin >> x >> y;
cout << ask(d[x], d[y]) << "\n";
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t = 1;
while (t--) {
solve();
}
return 0;
}
::::

浙公网安备 33010602011771号