C. Concat (X-th)

爆搜

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

using namespace std;

int main() {
    int n, k, x;
    cin >> n >> k >> x;
    
    vector<string> S(n);
    rep(i, n) cin >> S[i];
    
    vector<int> pw(k+1);
    pw[0] = 1;
    rep(i, k) pw[i+1] = pw[i]*n;
    
    vector<string> cand;
    rep(t, pw[k]) {
        string ns;
        rep(i, k) ns += S[t/pw[i]%n];
        cand.push_back(ns);
    }
    
    ranges::sort(cand);
    
    cout << cand[x-1] << '\n';
    
    return 0;
}

// 类似dp转移
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;

int main() {
    int n, k, x;
    cin >> n >> k >> x;
    
    vector<string> S(n);
    rep(i, n) cin >> S[i];
    
    vector<string> cand;
    cand.push_back("");
    rep(i, k) {
        vector<string> old;
        swap(cand, old);
        
        for (string ns : old) {
            rep(i, n) cand.push_back(ns+S[i]);
        }
    }
    
    ranges::sort(cand);
    
    cout << cand[x-1] << '\n';
    
    return 0;
}

// 递归
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;

int main() {
    int n, k, x;
    cin >> n >> k >> x;
    
    vector<string> S(n);
    rep(i, n) cin >> S[i];
    
    vector<string> cand;
    auto f = [&](auto& f, int i, string ns) -> void {
        if (i == k) {
            cand.push_back(ns);
            return;
        }
        
        rep(j, n) f(f, i+1, ns+S[j]);
    };
    f(f, 0, "");
    
    ranges::sort(cand);
    
    cout << cand[x-1] << '\n';
    
    return 0;
}

D. Match, Mod, Minimize 2

由于所有的 \(A_i\)\(B_i\) 均小于 \(M\),因此 \(A_i + B_i \bmod M\) 的结果只能是 \(A_i+B_i\)\(A_i +B_i - M\)

于是问题转化为:最多能组成多少个满足 \(A_i + B_i \geqslant M\) 的数对?

具体做法:

  • \(A_i\) 做降序排序
  • \(B_i\) 做升序排序
  • 为每个 \(A_i\) 寻找最小的满足 \(B_j \geqslant M-A_i\)\(B_j\) 进行配对,可以用双指针来实现
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;

void solve() {
    int n, m;
    cin >> n >> m;
    
    vector<int> a(n), b(n);
    rep(i, n) cin >> a[i];
    rep(i, n) cin >> b[i];
    
    ranges::sort(a, greater<>());
    ranges::sort(b);
    
    ll ans = 0;
    rep(i, n) ans += a[i]+b[i];
    
    int bi = 0;
    for (int na : a) {
        while (bi < n and na+b[bi] < m) bi++;
        if (bi == n) break;
        ans -= m;
        bi++;
    }
    
    cout << ans << '\n';
}

int main() {
    int t;
    cin >> t;
    
    while (t--) solve();
    
    return 0;
}

E. Development

本题是 \(\operatorname{floyd}\) 的变种

道路扩建的处理方式和ABC375F相同,都是 \(O(N^2)\)
机场扩建只需添加超级顶点,然后每个机场向这个点连一条边权为 \(T\) 的有向边,再连一条边权为 \(0\) 的反向边,即可采用相同逻辑实现!

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

using namespace std;
using ll = long long;

inline void chmin(ll& a, ll b) { if (a > b) a = b; }

int main() {
    int n, m;
    cin >> n >> m;
    n++;
    
    const ll INF = 1e18;
    vector d(n, vector<ll>(n, INF));
    rep(i, n) d[i][i] = 0;
    rep(i, m) {
        int a, b, c;
        cin >> a >> b >> c;
        chmin(d[a][b], c);
        chmin(d[b][a], c);
    }
    int k, T;
    cin >> k >> T;
    rep(i, k) {
        int a;
        cin >> a;
        chmin(d[a][0], T);
        chmin(d[0][a], 0);
    }
    
    rep(k, n)rep(i, n)rep(j, n) chmin(d[i][j], d[i][k]+d[k][j]);
    ll ans = 0;
    auto add = [&](int a, int b, int sign=1) {
        if (a == 0 or b == 0) return;
        if (d[a][b] != INF) ans += d[a][b]*sign;
    };
    rep(i, n)rep(j, n) add(i, j);
    
    auto upd = [&](int a, int b, ll c) {
        rep(i, n)rep(j, n) {
            add(i, j, -1);
            chmin(d[i][j], d[i][a]+c+d[b][j]);
            add(i, j);
        }
    };
    
    int q;
    cin >> q;
    rep(qi, q) {
        int type;
        cin >> type;
        if (type == 1) {
            int a, b, c;
            cin >> a >> b >> c;
            upd(a, b, c);
            upd(b, a, c);
        }
        else if (type == 2) {
            int a;
            cin >> a;
            upd(a, 0, T);
            upd(0, a, 0);
        }
        else {
            cout << ans << '\n';
        }
    }
    
    return 0;
}

F. Paint Tree 2

树形dp

dp[v][i][j] 表示在以点 \(v\) 为根的子树中,选取 \(i\) 条路径,是否选取点 \(v\) 指向父节点的边时的最大值

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

using namespace std;
using ll = long long;

inline void chmax(ll& a, ll b) { if (a < b) a = b; }

int main() {
    int n, k;
    cin >> n >> k;
    
    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 dp(n, vector(k+1, vector<ll>(2)));
    auto f = [&](auto& f, int v, int p=-1) -> void {
        vector dp2(k+1, vector<ll>(3));
        for (int u : to[v]) {
            if (u == p) continue;
            f(f, u, v);
            for (int i = k; i >= 0; --i) for (int j = 2; j >= 0; --j) {
                rep(ni, k+1)rep(nj, 2) {
                    if (i+ni > k or j+nj > 2) break;
                    chmax(dp2[i+ni][j+nj], dp2[i][j]+dp[u][ni][nj]);
                }
            }
        }
        rep(i, k+1) {
            chmax(dp[v][i][0], dp2[i][0]);
            chmax(dp[v][i][1], dp2[i][1]+a[v]);
            if (i < k) chmax(dp[v][i+1][0], dp2[i][2]+a[v]);
        }
    };
    f(f, 0);
    
    ll ans = dp[0][k][0];
    cout << ans << '\n';
    
    return 0;
}

G. Concat (1st)

\(K = \infty\) 时(经典问题解法):

  • 定义字典序规则:当 \(X+Y \leqslant Y+X\) 时,\(X\) 更小
  • 答案即为无限玄幻该规则下的最小字符串

\(K\) 有限时:

实际答案为上述无限解的前 \(K\) 位的前缀
考虑dp
dp[i][j] 表示使用 \(i\) 个字符串时,构造周期字符串前 \(j\) 位的最小总长度