C. Except and Min

注意到 \(K \leqslant 5\),只需检测序列 \(A\) 的前 \(6\) 小的数即可

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;

int main()  {
    int n, q;
    cin >> n >> q;
    
    vector<int> a(n);
    rep(i, n) cin >> a[i];
    
    vector<int> is(n);
    rep(i, n) is[i] = i;
    sort(is.begin(), is.end(), [&](int i, int j) {
        return a[i] < a[j];
    });
    
    const int INF = 1001001001;
    rep(qi, q) {
        int k;
        cin >> k;
        vector<int> b(k);
        rep(i, k) cin >> b[i], b[i]--;
        
        int ans = INF;
        rep(i, 6) {
            bool ng = false;
            for (int j : b) if (is[i] == j) ng = true;
            if (!ng) { ans = min(ans, a[is[i]]);  break; }
        }
        cout << ans << '\n';
    }
    
    return 0;
}

D. Integer-duplicated Path

树上 \(\text{dfs}\)
对于每一条路径,维护路径上所有数的出现次数,以及至少出现 \(2\) 次的不同数的个数

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;

int main()  {
    int n;
    cin >> n;
    
    vector<int> a(n);
    rep(i, n) cin >> a[i];
    
    vector<vector<int>> to(n);
    rep(i, n-1) {
        int u, v;
        cin >> u >> v;
        --u; --v;
        to[u].push_back(v);
        to[v].push_back(u);
    }
    
    vector<bool> ans(n);
    map<int, int> cnt; int dupCnt = 0;
    auto add = [&](int x) {
        cnt[x]++;
        if (cnt[x] == 2) dupCnt++;
    };
    auto del = [&](int x) {
        if (cnt[x] == 2) dupCnt--;
        cnt[x]--;
    };
    
    auto dfs = [&](auto& f, int v, int p=-1) -> void {
        add(a[v]);
        ans[v] = dupCnt > 0;
        for (int u : to[v]) {
            if (u == p) continue;
            f(f, u, v);
        }
        del(a[v]);
    };
    dfs(dfs, 0);
    
    rep(i, n) if (ans[i]) puts("Yes"); else puts("No");
    
    return 0;
}

E. Simple Division

\(P=10007\)
\(B=MP\)
\(N\) 写成 \(N=tB+b\),其中 \(0 \leqslant b < B\),即 \(b = N \bmod B\)

\[\lfloor\frac{N}{M}\rfloor = tP + \lfloor\frac{b}{M}\rfloor \]

两边分别对 \(P\) 取模得

\[\lfloor\frac{N}{M}\rfloor \bmod P = \lfloor\frac{b}{M}\rfloor \bmod P \]

于是我们只需计算 \(b=N \bmod B\) 即可。

代码实现
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;
using P = pair<int, int>;
using mint = modint;

const int p = 10007;

int main()  {
    int k, m;
    cin >> k >> m;
    
    vector<P> cl;
    rep(i, k) {
        int c, l;
        cin >> c >> l;
        cl.emplace_back(c, l);
    }
    
    auto pow = [&](mint x, int l) {
        mint res = 1;
        while (l) {
            if (l&1) res *= x;
            x *= x;
            l >>= 1;
        }
        return res;
    };
    auto repunit = [&](int l) {
        mint res = 0, x = 1, ten = 10;
        while (l) {
            if (l&1) res = res*ten + x;
            x = x*ten + x;
            ten *= ten;
            l >>= 1;
        }
        return res;
    };
    auto f = [&]() {
        mint res = 0;
        for (auto [c, l] : cl) {
            res = res*pow(10, l) + repunit(l)*c;
        }
        return res;
    };
    
    mint::set_mod(m*p);
    ll n = f().val();
    mint::set_mod(p);
    
    mint ans = mint(n/m);
    cout << ans.val() << '\n';
    
    return 0;
}

F. Authentic Traveling Salesman Problem

核心思路:分块排序(Zigzag Sorting)

由于坐标范围很大(\(2 \times 10^7\)),而总距离限制相对较宽松,最简单且有效的方法是使用类似于 莫队 的分块排序思想:

  1. 将平面划分为若干个水平分块(Strips):我们将 \(Y\) 轴方向划分为 \(B\) 个块。
  2. 块内排序
  • 对于位于同一个块内的点,按照 \(X\) 坐标进行排序。
  • 蛇形走位(Zigzag):为了减小从一个块跨越到下一个块时的突跳距离,我们在偶数块按 \(X\) 升序排,在奇数块按 \(X\) 降序排。
  1. 确定起点:题目要求从位置 1 开始。我们可以先对所有点排序,然后找到位置 1 在排序后序列中的位置,旋转序列使其以 1 开头。

为什么这个方法有效?

假设我们将平面分为 \(\sqrt{N}\) 个块。

  • 在块内移动时,\(X\) 方向的总位移约为 \(X_{max}\),一共有 \(\sqrt{N}\) 个块,总计 \(\sqrt{N} \cdot X_{max}\)
  • \(Y\) 方向,每个点平均移动 \(Y_{max} / \sqrt{N}\),总计 \(N \cdot (Y_{max} / \sqrt{N}) = \sqrt{N} \cdot Y_{max}\)
  • 代入数据:\(245 \times 2 \cdot 10^7 + 245 \times 2 \cdot 10^7 \approx 9.8 \times 10^9\)。这个值刚好在 \(10^{10}\) 的限制之内。
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;

int main()  {
    int q;
    cin >> q;
    
    vector<int> x(q), y(q);
    rep(i, q) cin >> x[i] >> y[i];
    
    const int n = 2e7;
    int D = max<int>(1, n/sqrt(q));
    
    vector<int> blockId(q);
    rep(i, q) blockId[i] = x[i]/D;
    
    vector<int> is(q);
    rep(i, q) is[i] = i;
    sort(is.begin(), is.end(), [&](int i, int j) -> bool {
        if (blockId[i] != blockId[j]) return blockId[i] < blockId[j];
        return (y[i] < y[j]) ^ (blockId[i]%2);
    });
    
    rep(i, q) {
        if (is[i] == 0) {
            rotate(is.begin(), is.begin()+i, is.end());
        }
    }
    
    rep(i, q) cout << is[i]+1 << " \n"[i == q-1];
    
    return 0;
}