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;
}
posted @ 2025-05-06 08:55  hhoppitree  阅读(217)  评论(0)    收藏  举报