2026.9.12 考试总结
考试总结
估:100 + 100 + 30 + 100
实:100 + 100 + 50 + 100 rk1
1 年前的 max ak 了。 /bx
演出
小柯共有 \(n\) 个朋友,所有朋友编号为 \(1\) 到 \(n\) ,当且仅当小柯给了第 \(i\) 个朋友门票或编号为 \([l_i, r_i]\) 中的朋友中至少有一人去了,第 \(i\) 个朋友才会去。小柯希望所有朋友都能来看,请问小柯至少要给多少人门票呢? \(n \le 5 \times 10 ^ 5\)
做法很显然。将 \([l_i, r_i]\) 中的点对 \(i\) 连边,最终求一边塔尖。我们发现最后没有入度的强联通分量显然一定会占用一张票,直接数即可。线段树优化建图可以做到 \(O(n \log n)\),做完了。
code
#include <bits/stdc++.h>
using namespace std;
#define int int
#define ll long long
#define usd unsigned
#define el putchar('\n')
#define lowbit(x) (x & (-x))
#define AC return
#define AK return 0
#define YS cout << "YES"
#define NO cout << "NO"
#define Ys cout << "Yes"
#define No cout << "No"
#define ys cout << "yes"
#define no cout << "no"
#define ls(i) ch[i][0]
#define rs(i) ch[i][1]
#define debug(num) cerr << #num << ' ' << num << '\n'
#define void inline void
#define il inline
#define Mod(x) (((x) % mod + mod) % mod)
#define pii pair <int, int>
#define pll pair <ll, ll>
il char gc() { char c; while((c = getchar()) <= ' ') ; return c; }
il int rd() {
char c; int x, f = 1;
while(!isdigit(c = getchar())) if(c == '-') f *= -1;
x = c ^ 48;
while(isdigit(c = getchar())) x = (x << 3) + (x << 1) + (c ^ 48);
return x * f;
}
void ACehomoxue();
signed main() {
srand(time(0));
// freopen("performance.in", "r", stdin);
// freopen("performance.out", "w", stdout);
int t = 1;
// t = rd();
while(t--) ACehomoxue();
AK;
}
const int mod = 998244353, maxn = 2 * 1e6 + 18;
int n, ans = 0;
vector <int> vec[maxn];
vector <pii> e;
void add(int u, int v) { vec[u].push_back(v); e.push_back({u, v}); }
int tot;
class xds {
struct tree {
int l, r, id;
} t[maxn << 2];
public:
void build(int i, int l, int r) {
t[i].l = l, t[i].r = r, t[i].id = ++tot;
if(l == r) {
add(l, t[i].id);
AC;
}
int mid = l + r >> 1;
build(i << 1, l, mid);
build(i << 1 | 1, mid + 1, r);
add(t[i << 1].id, t[i].id);
add(t[i << 1 | 1].id, t[i].id);
}
void updata(int i, int l, int r, int x) {
if(t[i].l >= l && t[i].r <= r) {
add(t[i].id, x);
AC;
}
int mid = t[i].l + t[i].r >> 1;
if(l <= mid) updata(i << 1, l, r, x);
if(r > mid) updata(i << 1 | 1, l, r, x);
}
} ds;
int be[maxn], dfn[maxn], dn, low[maxn], m, in[maxn];
vector <int> stk;
bool vis[maxn];
void tarjan(int x) {
vis[x] = true;
stk.push_back(x);
dfn[x] = low[x] = ++dn;
for(int to : vec[x]) {
if(vis[to]) low[x] = min(low[x], dfn[to]);
if(!dfn[to]) {
tarjan(to);
low[x] = min(low[x], low[to]);
}
}
if(dfn[x] == low[x]) {
be[x] = ++m;
vis[x] = false;
while(stk.back() != x) {
int i = stk.back();
be[i] = m;
vis[i] = false;
stk.pop_back();
}
stk.pop_back();
}
}
queue <int> qu;
void ACehomoxue() {
n = rd();
tot = n;
ds.build(1, 1, n);
for(int i = 1, l, r; i <= n; i++) {
l = rd(), r = rd();
ds.updata(1, l, r, i);
}
for(int i = 1; i <= tot; i++) if(!dfn[i]) tarjan(i);
for(pii tmp : e) {
int u = be[tmp.first], v = be[tmp.second];
if(u == v) continue;
in[v]++;
}
for(int i = 1; i <= m; i++) if(!in[i]) ans++;
cout << ans;
el;
}
指挥
改编自:P12846
把原题中的改变硬币翻面换成了全部翻上来或翻下去。
我们考虑全是操作全翻转和查询那么就是区间修改线段树,加上 \(l + 2k\) 或 \(l + 3k\),我们将每个数按照对 \(6\) 取模后的余数分类,类似开 \(6\) 个线段树,我们发现就可以将其全部转化为只有全翻转和全查询。做完了。
code
#include <bits/stdc++.h>
using namespace std;
#define int int
#define ll long long
#define usd unsigned
#define el putchar('\n')
#define lowbit(x) (x & (-x))
#define AC return
#define AK return 0
#define YS cout << "YES"
#define NO cout << "NO"
#define Ys cout << "Yes"
#define No cout << "No"
#define ys cout << "yes"
#define no cout << "no"
#define ls(i) ch[i][0]
#define rs(i) ch[i][1]
#define debug(num) cerr << #num << ' ' << num << '\n'
#define void inline void
#define il inline
#define Mod(x) (((x) % mod + mod) % mod)
#define pii pair <int, int>
#define pll pair <ll, ll>
il char gc() { char c; while((c = getchar()) <= ' ') ; return c; }
il int rd() {
char c; int x, f = 1;
while(!isdigit(c = getchar())) if(c == '-') f *= -1;
x = c ^ 48;
while(isdigit(c = getchar())) x = (x << 3) + (x << 1) + (c ^ 48);
return x * f;
}
void ACehomoxue();
signed main() {
srand(time(0));
// freopen("command.in", "r", stdin);
// freopen("command.out", "w", stdout);
int t = 1;
// t = rd();
while(t--) ACehomoxue();
AK;
}
const int mod = 998244353, maxn = 5 * 1e5 + 18;
class xds {
struct tree {
int l, r, c[6], t[6], f[6];
} t[maxn << 2];
void push_up(int i) {
for(int j = 0; j < 6; j++) {
t[i].c[j] = t[i << 1].c[j] + t[i << 1 | 1].c[j];
}
}
#define mdf(i, j, x) t[i].f[j] = x; t[i].c[j] = t[i].t[j] * x;
void push_down(int i) {
for(int j = 0; j < 6; j++) {
if(t[i].f[j] == -1) continue;
mdf(i << 1, j, t[i].f[j]);
mdf(i << 1 | 1, j, t[i].f[j]);
t[i].f[j] = -1;
}
}
public:
void build(int i, int l, int r) {
t[i].l = l, t[i].r = r;
for(int j = 0; j < 6; j++) t[i].c[j] = 0, t[i].f[j] = -1;
if(l == r) {
t[i].t[l % 6] = 1;
AC;
}
int mid = l + r >> 1;
build(i << 1, l, mid);
build(i << 1 | 1, mid + 1, r);
for(int j = 0; j < 6; j++) t[i].t[j] = t[i << 1].t[j] + t[i << 1 | 1].t[j];
}
void updata(int i, int l, int r, int j, int x) {
if(t[i].l >= l && t[i].r <= r) {
mdf(i, j, x);
AC;
}
push_down(i);
int mid = t[i].l + t[i].r >> 1;
if(l <= mid) updata(i << 1, l, r, j, x);
if(r > mid) updata(i << 1 | 1, l, r, j, x);
push_up(i);
}
il int query(int i, int l, int r) {
if(t[i].l >= l && t[i].r <= r) {
int res = 0;
for(int j = 0; j < 6; j++) res += t[i].c[j];
return res;
}
push_down(i);
int mid = t[i].l + t[i].r >> 1, res = 0;
if(l <= mid) res += query(i << 1, l, r);
if(r > mid) res += query(i << 1 | 1, l, r);
return res;
}
} ds;
int n, q;
void ACehomoxue() {
n = rd(), q = rd();
ds.build(1, 1, n);
for(int opt, l, r, k; q--; ) {
opt = rd(), l = rd(), r = rd();
if(opt == 4) { cout << ds.query(1, l, r), el; continue; }
k = rd();
if(opt == 1) {
for(int j = 0; j < 6; j++) {
ds.updata(1, l, r, j, k);
}
}
if(opt == 2) {
for(int x = l; x <= l + 4; x += 2) {
int j = x % 6;
ds.updata(1, l, r, j, k);
}
}
if(opt == 3) {
for(int x = l; x <= l + 3; x += 3) {
int j = x % 6;
ds.updata(1, l, r, j, k);
}
}
}
}
配对
原题:P12729 [KOI 2021 Round 2] 最长公共括号子串
首先对于找合法序列是简单的,将 ( 换为 \(1\) 将 ) 换为 \(-1\),那么对于区间 \((l, r]\) 合法的条件有 \(sum_l = sum_r\) 且 \(\forall i \in (l, r]\) 有 \(sum_i \le sum_l\)。现在我们考虑要为两个的字串,我们建出广义 sam,对于节点 \(i\) 在 \(A\) 和 \(B\) 中都出现过,那么我们记其在 \(A\) 中出现的位置 \(pos_i\),由于这个节点存了一个后缀,那么当 \(r = pos_i\) 时,显然有 \(l \in [r - len_i, r)\),那么我们直接二分找最远的符合条件的 \(l\) 即可。做完了。
code
#include <bits/stdc++.h>
using namespace std;
#define int int
#define ll long long
#define usd unsigned
#define el putchar('\n')
#define lowbit(x) (x & (-x))
#define AC return
#define AK return 0
#define YS cout << "YES"
#define NO cout << "NO"
#define Ys cout << "Yes"
#define No cout << "No"
#define ys cout << "yes"
#define no cout << "no"
#define ls(i) ch[i][0]
#define rs(i) ch[i][1]
#define debug(num) cerr << #num << ' ' << num << '\n'
#define void inline void
#define il inline
#define Mod(x) (((x) % mod + mod) % mod)
#define pii pair <int, int>
#define pll pair <ll, ll>
il char gc() { char c; while((c = getchar()) <= ' ') ; return c; }
il int rd() {
char c; int x, f = 1;
while(!isdigit(c = getchar())) if(c == '-') f *= -1;
x = c ^ 48;
while(isdigit(c = getchar())) x = (x << 3) + (x << 1) + (c ^ 48);
return x * f;
}
void ACehomoxue();
signed main() {
srand(time(0));
// freopen(".in", "r", stdin);
// freopen(".out", "w", stdout);
int t = 1;
t = rd();
while(t--) ACehomoxue();
AK;
}
const int mod = 998244353, maxn = 2 * 1e6 + 18, maxk = 25;
int n, m, a[maxn], Log[maxn], nxt[maxn][maxk], ans = 0;
il int get(int l, int r) {
int k = Log[r - l + 1];
return min(nxt[l][k], nxt[r - (1 << k) + 1][k]);
}
vector <int> pos[maxn];
string aa, bb;
int tot = 0, from = 0, to = 0;
struct state {
int link, len, pos;
bool has1, has2;
map <int, int> ch;
void clr() { has1 = false, has2 = false; link = len = 0; ch.clear(); }
} s[maxn * 2];
vector <int> vec[maxn];
int ask[maxn];
void init() { for(; tot + 1; tot--) s[tot].clr(), vec[tot].clear(); tot = 0, from = 0, to = 0; s[0].link = -1; }
void insert(char c, int type, int id) {
int i;
if(c == '(') i = 0;
else if(c == ')') i = 1;
else i = 2;
to = ++tot;
s[to].pos = id;
s[to].len = s[from].len + 1;
int q = 0, clone = 0;
for(int p = from; p != -1; p = s[p].link) {
if(q) {
if(s[p].ch[i] == q) { s[p].ch[i] = clone; continue; }
else break;
}
if(!s[p].ch[i]) { s[p].ch[i] = to; continue; }
q = s[p].ch[i];
if(s[p].len + 1 == s[q].len) { s[to].link = q; break; }
clone = ++tot;
s[clone] = s[q];
s[clone].len = s[p].len + 1;
s[to].link = s[q].link = clone;
s[p].ch[i] = clone;
}
from = to;
if(type == 1) s[to].has1 = true;
if(type == 2) s[to].has2 = true;
}
void dfs(int x) {
for(int to : vec[x]) {
dfs(to);
s[x].has1 |= s[to].has1;
s[x].has2 |= s[to].has2;
}
}
void solve() {
for(int i = 0; i <= tot; i++) {
if(s[i].link != i) {
vec[s[i].link].push_back(i);
}
}
dfs(0);
for(int p = 1; p <= tot; p++) {
if(!s[p].has1 || !s[p].has2) continue;
int i = s[p].pos, len = s[p].len;
ask[i] = max(ask[i], len);
}
}
void ACehomoxue() {
init(), ans = 0;
cin >> aa >> bb;
n = aa.length(), m = bb.length();
Log[0] = -1;
nxt[0][0] = 1e6;
for(int i = 1; i <= n; i++) {
Log[i] = Log[i / 2] + 1;
if(aa[i - 1] == '(') a[i] = 1;
else a[i] = -1;
nxt[i][0] = nxt[i - 1][0] + a[i];
}
for(int k = 1; k < maxk; k++) {
for(int i = 0; i + (1 << k) <= n + 1; i++) {
nxt[i][k] = min(nxt[i][k - 1], nxt[i + (1 << k - 1)][k - 1]);
}
}
for(int i = 1; i <= n; i++) insert(aa[i - 1], 1, i);
insert('+', 0, 0);
for(int i = 1; i <= m; i++) insert(bb[i - 1], 2, i);
solve();
pos[nxt[0][0]].push_back(0);
for(int i = 1, sum, l, r; i <= n; i++) {
sum = nxt[i][0];
r = pos[sum].size() - 1, l = lower_bound(pos[sum].begin(), pos[sum].end(), i - ask[i]) - pos[sum].begin();
ask[i] = 0;
while(l <= r) {
int mid = l + r >> 1, p = pos[sum][mid];
if(get(p, i) >= sum) {
ans = max(ans, i - p);
r = mid - 1;
} else l = mid + 1;
}
pos[sum].push_back(i);
}
for(int i = 0; i <= n; i++) pos[nxt[i][0]].clear();
cout << ans;
el;
}
台阶
原题:ReTravel,洛谷上的 link,不知道为啥 atcoder better 搜不到。
水 dp。
我们发现每当 \(x\) 或 \(y\) 坐标有一个增加 \(1\) 时,花费就需增加 \(1\),那么显然两点间最短代价为 \(f(i, j) = \mid x_i - x_j \mid + \mid y_i - y_j \mid\)。由于可以回退,那么我们不难发现我们走的路会形成一颗树,且对于每一个 \([l, r]\),我们让其树的节点尽量多是不劣的,即对于我们要先走 \([l, k]\) 再走 \((k, r]\),直接依次走 \([l, r]\) 不优于再弄一个中转点,从其先走到 \([l, k]\) 然后退回,再去走 \((k, r]\)。那我们就可以对树的形态 dp 了,\(dp_{l, r}\) 表示从子树 \([l, r]\) 的根依次走完 \([l, r]\) 的最小代价,我们可以枚举 \(k\) 将 \([l, k]\) 和 \((k, r]\) 拼起来,并且不难发现我们让中转点到每个点的距离最短不劣,那么对于任意区间 \([l, r]\) 我们设其根为 \(rt_{[l, r]}\),那么显然其 \(x\) 和 \(y\) 左边都应为 \([l, r]\) 内最小的 \(x\) 或 \(y\),那么直接转移就有:
做完了,注意有可能最后 \(rt_{[1, n]}\) 不为原点,要加上原点到 \(rt_{[1, n]}\) 的代价。

浙公网安备 33010602011771号