IOI 2021 & 2022 中国国家候选队 / 集训队互测做题记录
IOI 2021 中国国家候选队互测
| 场次 | \(\quad\qquad\text{A}\qquad\quad\) | 完成情况 | \(\quad\qquad\text{B}\qquad\quad\) | 完成情况 | \(\quad\qquad\text{C}\qquad\quad\) | 完成情况 |
|---|---|---|---|---|---|---|
| \(\text{Round 1}\) | 太阳神的宴会 | 三维立体混元劲 | 数圈圈 | |||
| \(\text{Round 2}\) | 逛公园 | 快递公司 | 小 C 的比赛 |
IOI 2022 中国国家候选队互测
| 场次 | \(\quad\qquad\text{A}\qquad\quad\) | 完成情况 | \(\quad\qquad\text{B}\qquad\quad\) | 完成情况 | \(\quad\qquad\text{C}\qquad\quad\) | 完成情况 |
|---|---|---|---|---|---|---|
| \(\text{Round 1}\) | 毛估估就行 | \(\color{green}\checkmark\) | 理论复杂度 | 广为人知题 | ||
| \(\text{Round 2}\) | 枪打出头鸟 | 伟大 NIT 的贸易计划 | 可爱多的字符串 |
IOI 2022 中国国家集训队互测
| 场次 | \(\quad\qquad\text{A}\qquad\quad\) | 完成情况 | \(\quad\qquad\text{B}\qquad\quad\) | 完成情况 | \(\quad\qquad\text{C}\qquad\quad\) | 完成情况 |
|---|---|---|---|---|---|---|
| \(\text{Round 1}\) | 愚蠢的在线法官 | 这是一道集训队胡策题 | 树上的孤独 | |||
| \(\text{Round 2}\) | 序列 | \(\color{green}\checkmark\) | Imbalance | \(\color{green}\checkmark\) | 学姐买瓜 | \(\color{green}\checkmark\) |
| \(\text{Round 3}\) | Lovely Dogs | Alice、Bob 与 DFS | 音符大师 | |||
| \(\text{Round 4}\) | 基础图论练习题 | \(\color{green}\checkmark\) | 机器 | 数列重排 | ||
| \(\text{Round 5}\) | Speike & Tom | 聚会 | 细菌 | |||
| \(\text{Round 6}\) | 圆滚滚的算术占卜 | 交朋友 | 球球 | |||
| \(\text{Round 7}\) | djq 学生物 | 完全表示 | 关于因为与去年互测zjk撞题而不得不改题这回事 | |||
| \(\text{Round 8}\) | Numbers | 造数据 | WereYouLast | |||
| \(\text{Round 9}\) | 子集匹配 | Slight Hope | 海胆 | |||
| \(\text{Round 10}\) | 抽奖机 | 中奖率 | 染色 | |||
| \(\text{Round 11}\) | Tree | 挑战分解质因数 | 匹配计数 | |||
| \(\text{Round 12}\) | 生活在对角线下 | 整数 | 蜘蛛爬树 |
\(\color{blue}\mathbf{Round\;2}\)
序列 - 张庭瑞
发现 \(a_i+a_j+a_k-\max(a_i,a_j,a_k)-\min(a_i,a_j,a_k)\) 等于 \(a_i,a_j,a_k\) 中的中位数。
更进一步的,我们有 \(a_i,a_j,a_k\) 的中位数为 \(x\) 的充要条件为:它们之中的不存在两个不同的数(指来源不同而非值不同)同时小于 \(x\) 或同时大于 \(x\),证明可以通过分类讨论或者根据与 \(x\) 的大小关系转化为 \(-1,0,1\) 后考虑。
因此,我们可以将一条限制拆成 \(12\) 条:
- \(a_i<x\Rightarrow a_j\ge x\);
- \(a_i<x\Rightarrow a_k\ge x\);
- \(a_j<x\Rightarrow a_i\ge x\);
- \(a_j<x\Rightarrow a_k\ge x\);
- \(a_k<x\Rightarrow a_i\ge x\);
- \(a_k<x\Rightarrow a_j\ge x\);
- \(a_i>x\Rightarrow a_j\le x\);
- \(a_i>x\Rightarrow a_k\le x\);
- \(a_j>x\Rightarrow a_i\le x\);
- \(a_j>x\Rightarrow a_k\le x\);
- \(a_k>x\Rightarrow a_i\le x\);
- \(a_k>x\Rightarrow a_j\le x\)。
所以,可以对于每个 \(a_i\) 在值域上建立节点(对于一次操作 \(i,j,k,x\),分别对 \(a_i,a_j,a_k\) 建立两对对偶节点,表示 \(<x\) / \(\ge x\) 以及 \(\le x\) / \(>x\)),建立限制依赖关系构成的图后使用 2-SAT 解决该问题,总时间复杂度 \(\mathcal{O}(n+m)\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5, L = N * 16, V = 1e9;
vector<int> G[L];
int tot;
map<int, array<int, 2> > M[N];
int dfn[L], low[L], dfn_c, inS[L];
stack<int> S;
int bcc[L], bcc_c;
void Tarjan(int x) {
dfn[x] = low[x] = ++dfn_c, inS[x] = 1, S.push(x);
for (auto v : G[x]) {
if (!dfn[v]) Tarjan(v), low[x] = min(low[x], low[v]);
else if (inS[v]) low[x] = min(low[x], dfn[v]);
}
if (low[x] == dfn[x]) {
++bcc_c;
int v;
do {
v = S.top(), S.pop(), inS[v] = 0;
bcc[v] = bcc_c;
} while (v != x);
}
}
int usd[L];
signed main() {
int n, m; scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) {
M[i][0][0] = ++tot, M[i][0][1] = ++tot;
M[i][V + 1][0] = ++tot, M[i][V + 1][1] = ++tot;
}
for (int i = 1; i <= m; ++i) {
int a, b, c, w; scanf("%d%d%d%d", &a, &b, &c, &w);
for (auto v : {a, b, c}) {
if (!M[v].count(w)) M[v][w][0] = ++tot, M[v][w][1] = ++tot;
if (!M[v].count(w + 1)) M[v][w + 1][0] = ++tot, M[v][w + 1][1] = ++tot;
}
vector< pair<int, int> > p = {{a, b}, {b, a}, {a, c}, {c, a}, {b, c}, {c, b}};
for (auto [x, y] : p) {
G[M[x][w][0]].push_back(M[y][w][1]);
G[M[x][w + 1][1]].push_back(M[y][w + 1][0]);
}
}
for (int i = 1; i <= n; ++i) {
int l1 = -1, l2 = -1;
for (auto [t, x] : M[i]) {
auto [a, b] = x;
if (~l1) G[l1].push_back(a);
if (~l2) G[b].push_back(l2);
l1 = a, l2 = b;
}
}
for (int i = 1; i <= tot; ++i) {
if (!dfn[i]) Tarjan(i);
}
for (int i = 1; i <= tot; i += 2) {
if (bcc[i] == bcc[i + 1]) return 0 & puts("NO");
if (bcc[i] < bcc[i + 1]) usd[i] = 1;
else usd[i + 1] = 1;
}
puts("YES");
for (int i = 1; i <= n; ++i) {
int ans = 1;
for (auto [t, x] : M[i]) {
if (usd[x[1]]) ans = t;
}
printf("%d%c", ans, " \n"[i == n]);
}
return 0;
}
Imbalance - 杨珖
如果将 \(0\) 视作往右下走,\(1\) 视作往右上走,则可以将 \(01\) 序列画为一条折线,其中不存在两个距离为 \(k\) 的等高点。根据介值定理,要么所有段都有 \(>\dfrac{k}{2}\) 个 \(1\),要么所有段都有 \(<\dfrac{k}{2}\) 个 \(1\),不妨假设为前者。
容易得到一个 \(\mathcal{O}(n2^k)\) 的暴力状态压缩 dp,对数据进行分治后,我们只需要考虑 \(k\ge20\) 的情况。为了方便起见,我们不妨假设 \(n\) 是 \(k\) 的倍数(这可以通过在原序列前加上若干个 \(1\) 来实现)。
那么此时我们将这个平面视作一个环,或者说,走到直线 \(x=k\) 的时候会被强制传送至 \(x\) 轴上(保留 \(y\) 坐标不变),那么条件转化为每条折线都要严格在上一条折线的上方,我们枚举每条折线的端点后可以使用 LGV 引理直接计算,时间复杂度 \(\mathcal{O}\left(\left(\frac{k}{2}\right)^LL^3+\text{poly}(n,m,L)\right)\),其中 \(L=\dfrac{n}{k}\le6\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 120, P = 998244353;
void add(int &x, int y) {
((x += y) >= P) && (x -= P);
}
int edp[N], a[10][10];
struct MOD {
long long B;
int operator ()(long long x) {
return x - (((__int128)x * 18479187002) >> 64) * P;
}
} mod;
int det(int n) {
int res = 1;
for (int i = 1; i <= n; ++i) {
int wh = -1;
auto tr = [&](int &x) {
x -= (x >= P ? P : 0);
};
for (int j = i; j <= n; ++j) {
tr(a[j][i]);
if (a[j][i] && (!~wh || a[j][i] < a[wh][i])) {
wh = j;
}
}
if (!~wh) {
return 0;
}
if (i != wh) {
swap(a[i], a[wh]);
res = P - res;
}
for (int j = i + 1; j <= n; ++j) if (a[j][i]) {
while (1) {
tr(a[j][i]), tr(a[i][i]);
int tmp = P - a[j][i] / a[i][i], *fi = a[j], *se = a[i];
for (int k = i; k <= n; k += 4) {
fi[k] = mod(fi[k] + 1ll * se[k] * tmp);
fi[k + 1] = mod(fi[k + 1] + 1ll * se[k + 1] * tmp);
fi[k + 2] = mod(fi[k + 2] + 1ll * se[k + 2] * tmp);
fi[k + 3] = mod(fi[k + 3] + 1ll * se[k + 3] * tmp);
}
tr(fi[i]);
if (!fi[i]) break;
swap(a[i], a[j]);
res = P - res;
}
}
res = mod(1ll * res * a[i][i]);
}
return (res % P + P) % P;
}
int tc, ans, nxt[N][N << 1], val[N][N << 1][N];
int getVal(int fr, int w, int y) {
if (~val[fr][w][y + N]) return val[fr][w][y + N];
memset(val[fr], 0, sizeof(val[fr]));
val[fr][0][fr + N] = 1;
for (int i = 0; i < w; ++i) {
for (int j = fr - i + N; j <= fr + i + N; j += 2) {
if (nxt[i][j] != -1) add(val[fr][i + 1][j + 1], val[fr][i][j]);
if (nxt[i][j] != 1) add(val[fr][i + 1][j - 1], val[fr][i][j]);
}
}
return val[fr][w][y + N];
}
int calc(int n, int L) {
int cnt = n / L;
for (int i = 1; i <= cnt; ++i) {
for (int j = 1; j <= cnt; ++j) {
a[i][j] = getVal((i == 1 ? 0 : edp[i - 2]), L, edp[j - 1]);
}
}
return det(cnt);
}
void dfs(int x, int n, int L) {
if (x == tc) {
add(ans, calc(n, L));
return;
}
for (int i = 2; i <= L; i += 2) {
edp[x] = (x == 0 ? 0 : edp[x - 1]) + i;
dfs(x + 1, n, L);
}
}
int Solve(int n, int L, string S) {
while (n % L) {
S = "1" + S, ++n;
}
memset(nxt, 0, sizeof(nxt)), memset(val, -1, sizeof(val));
for (int i = 0, cur = 0; i < (int)S.size(); ++i) {
int val = (S[i] == '1' ? 1 : -1);
if (nxt[i % L][cur + N] && nxt[i % L][cur + N] != val) {
return 0;
}
nxt[i % L][cur + N] = val;
cur += val;
}
ans = 0, tc = n / L, dfs(0, n, L);
return ans;
}
int cnt[1 << 20], f[N][1 << 20];
signed main() {
int n, L, m; scanf("%d%d%d", &n, &L, &m);
string S;
if (m != 0) cin >> S;
if (L <= 20) {
for (int i = 1; i < (1 << L); ++i) {
cnt[i] = cnt[i >> 1] + (i & 1);
}
for (int i = 0; i < (1 << L); ++i) {
int fl = 1;
for (int j = 0; j < m; ++j) {
if (((i >> j) & 1) != S[j] - '0') fl = 0;
}
if (fl) ++f[L - 1][i];
}
for (int i = L - 1; i + 1 < n; ++i) {
for (int j = 0; j < (1 << L); ++j) {
if (!f[i][j] || cnt[j] == L / 2) continue;
add(f[i + 1][j >> 1], f[i][j]);
add(f[i + 1][(j >> 1) | (1 << (L - 1))], f[i][j]);
}
}
int res = 0;
for (int i = 0; i < (1 << L); ++i) {
if (cnt[i] != L / 2) add(res, f[n - 1][i]);
}
printf("%d\n", res);
return 0;
}
int res = Solve(n, L, S);
for (auto &x : S) x ^= 1;
add(res, Solve(n, L, S));
printf("%d\n", res);
return 0;
}
学姐买瓜 - 张家瑞
下文视 \(n,m\) 同阶,则首先可以得到一个 \(\tilde{\mathcal{O}}(n^2)\) 的暴力:每次询问将区间内的线段按照 \(r\) 排序后从左往右贪心选取线段。
注意到,如果两个区间为包含关系,那么较长的一个区间可以不用考虑,而我们可以通过数据结构高效维护所有有效的区间。
如果不存在相互包含的区间,这个贪心可以用等价的语言进行描述:假设有 \(n+1\) 个点,对于区间 \([l,r]\),我们添加一条 \(l\to r+1\) 的边,边权为 \(1\);如果对于一个位置 \(i\) 找不到这样的 \(l\),那么就向 \(i+1\) 连一条边权为 \(0\) 的边。容易发现这构成了一棵以 \(n+1\) 为根的树形结构,而每次询问的答案就是 \(l\) 在不超过 \(r+1\) 的前提下不断跳父亲所经过的边权和,这可以使用 LCT 在 \(\mathcal{O}(n\log n)\) 的复杂度内维护。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 5;
int nxt[N];
struct Node {
int val, sum;
int ch[2], fa;
bool rev;
} p[N];
#define ls(x) (p[x].ch[0])
#define rs(x) (p[x].ch[1])
#define fa(x) (p[x].fa)
#define ident(x, f) (rs(f) == x)
#define connect(x, f, s) (p[fa(x) = f].ch[s] = x)
#define reverse(x) (p[x].rev ^= 1, swap(ls(x), rs(x)))
#define nroot(x) (ls(fa(x)) == x || rs(fa(x)) == x)
#define update(x) (p[x].sum = p[ls(x)].sum + p[rs(x)].sum + p[x].val)
void pushdown(int x) {
if (!p[x].rev) return;
if (ls(x)) reverse(ls(x));
if (rs(x)) reverse(rs(x));
p[x].rev = 0;
}
void pushall(int x) {
if (nroot(x)) pushall(fa(x));
pushdown(x);
}
void rotate(int x) {
int f = fa(x), ff = fa(f), wh = ident(x, f);
connect(p[x].ch[wh ^ 1], f, wh);
fa(x) = ff;
if (nroot(f)) {
p[ff].ch[ident(f, ff)] = x;
}
connect(f, x, wh ^ 1);
update(f), update(x);
}
void splay(int x) {
pushall(x);
while (nroot(x)) {
int f = fa(x), ff = fa(f);
if (nroot(f)) {
ident(x, f) ^ ident(f, ff) ? rotate(x) : rotate(f);
}
rotate(x);
}
}
void access(int x) {
for (int y = 0; x; splay(x), rs(x) = y, update(x), x = fa(y = x));
}
void mkroot(int x) {
access(x), splay(x), reverse(x);
}
int findroot(int x) {
access(x), splay(x);
while (ls(x)) {
pushdown(x), x = ls(x);
}
splay(x);
return x;
}
void link(int x, int y) {
mkroot(x);
if (findroot(y) == x) return;
fa(x) = y;
}
void cut(int x, int y) {
mkroot(x);
if (findroot(y) != x || fa(y) != x || ls(y)) return;
fa(y) = rs(x) = 0;
update(x);
}
void add(int l, int r) {
cut(l, l + 1), link(l, r + 1);
nxt[l] = r + 1, p[l].val = 1, update(l);
}
void del(int l, int r) {
cut(l, nxt[l]), link(l, l + 1);
p[l].val = 0, update(l);
}
signed main() {
int q, n; scanf("%d%d", &q, &n);
static int mn[N];
for (int i = 1; i <= n; ++i) mn[i] = n + 1;
set< pair<int, int> > S;
for (int i = 1; i <= n; ++i) {
nxt[i] = i + 1, link(i, i + 1);
}
while (q--) {
int opt, l, r; scanf("%d%d%d", &opt, &l, &r);
if (opt == 1) {
int fl = 1;
for (int i = n - l + 1; i; i -= i & -i) fl &= (mn[i] > r);
for (int i = n - l + 1; i <= n; i += i & -i) mn[i] = min(mn[i], r);
if (!fl) continue;
while (!S.empty()) {
auto it = S.lower_bound({l + 1, 0});
if (it != S.begin() && (--it)->second >= r) {
del(it->first, it->second), S.erase(it);
} else {
break;
}
}
add(l, r), S.insert({l, r});
} else {
mkroot(n + 1), access(l), splay(l);
int cur = l, lst = 0;
while (cur) {
pushdown(cur);
int dir = (cur > r);
if (dir) lst = cur;
if (!p[cur].ch[dir]) break;
cur = p[cur].ch[dir];
}
splay(cur), splay(lst);
printf("%d\n", p[rs(lst)].sum - (lst > r + 1));
}
}
return 0;
}

浙公网安备 33010602011771号