T1:Full Moon

模拟

代码实现
n, m, p = map(int, input().split())
ans = 0
i = m 
while i <= n:
    ans += 1
    i += p
print(ans)

或者答案是 \(\lfloor\frac{n+(p-m)}{p}\rfloor\)

T2:Overlapping sheets

模拟

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

using namespace std;

int main() {
    int n;
    cin >> n;
    
    int m = 100;
    vector s(m, vector<int>(m));
    rep(_, n) {
        int a, b, c, d;
        cin >> a >> b >> c >> d;
        for (int i = a; i < b; ++i) {
            for (int j = c; j < d; ++j) {
                s[i][j] = 1;
            }
        }
    }
    
    int ans = 0;
    rep(i, m)rep(j, m) if (s[i][j] == 1) ans++;
    
    cout << ans << '\n';
    
    return 0;
}

T3:Blue Spring

贪心

  • 先将 \(f\) 做升序排序
  • 预处理一下 \(f\) 的前缀和
  • 可以枚举后缀哪几个 \(d\) 天买一日游券,剩下的前缀就都是买常规票
代码实现
#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 n, d, p;
    cin >> n >> d >> p;
    
    vector<int> f(n);
    rep(i, n) cin >> f[i];
    
    sort(f.begin(), f.end());
    
    ll now = 0;
    rep(i, n) now += f[i];
    ll ans = now;
    while (f.size()) {
        ll s = 0;
        int sz = min<int>(d, f.size());
        rep(i, sz) {
            s += f.back();
            f.pop_back();
        }
        now -= s; now += p;
        ans = min(ans, now);
    }
    
    cout << ans << '\n';
    
    return 0;
}

T4:General Weighted Max Matching

注意到由于边权是按字典序给出的,所以只需要在边列表上做 \(\operatorname{dfs}\) 暴力枚举出所有可能的合法匹配即可

代码实现
#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 n;
    cin >> n;
    
    vector d(n+1, vector<int>(n+1));
    rep(i, n) {
        for (int j = i+1; j < n; ++j) {
            cin >> d[i][j];
            d[j][i] = d[i][j];
        }
    }
    if (n&1) n++;
    
    ll ans = 0;
    vector<bool> used(n);
    auto f = [&](auto f, ll w) -> void {
        ans = max(ans, w);
        int i = 0;
        while (i < n and used[i]) i++;
        if (i == n) return;
        used[i] = true;
        for (int j = i+1; j < n; ++j) {
            if (used[j]) continue;
            used[j] = true;
            f(f, w+d[i][j]);
            used[j] = false;
        }
        used[i] = false;
    };
    f(f, 0);
    
    cout << ans << '\n';
    
    return 0;
}

T5:Sandwiches

容斥,无视条件 \(3\) 的方案数减去满足 \(A_i = A_j = A_k\) 的方案数

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

using namespace std;
using ll = long long;

ll c3(ll n) { // nC3
    return n*(n-1)*(n-2)/6;
}

int main() {
    int n;
    cin >> n;
    
    vector<int> a(n);
    rep(i, n) cin >> a[i];
    
    vector<int> cnt(n+1);
    vector<ll> sum(n+1);
    ll ans = 0;
    rep(k, n) {
        ans += ll(k-1)*cnt[a[k]] - sum[a[k]];
        cnt[a[k]]++;
        sum[a[k]] += k;
    }
    
    rep(i, n) ans -= c3(cnt[i+1]);
    cout << ans << '\n';
    
    return 0;
}

T6:Octopus

考虑枚举 \(k\),那么我们可以在 \(\mathcal{O}(N\log N)\) 的时间里判断是否满足条件:将所有的 \(|X_i-k|\) 排序,得到序列 \(d_i\),判断是否所有 \(d_i\leqslant L_i\) 即可。

注意到我们只关心选择的 \(k\) 是不是能让第 \(j\) 个机械臂取到 \(X_i\),那么我们把所有的 \(X_i-L_j-1\)\(X_i+L_j\) 作为关键点,则任意两个关键点之间(左开右闭)的数本质都是一样的 ,要么同时满足,要么同时不满足。

于是要判断的数的个数变成了 \(N^2\) ,总时间复杂度 \(\mathcal{O}(N^3\log N)\)

代码实现
#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 n;
    cin >> n;
    
    vector<ll> x(n), l(n);
    rep(i, n) cin >> x[i];
    rep(i, n) cin >> l[i];
    
    sort(l.begin(), l.end());
    
    set<ll> ps;
    rep(i, n)rep(j, n) {
        ps.insert(x[i]-l[j]-1);
        ps.insert(x[i]+l[j]);
    }
    
    ll ans = 0;
    ll pre = 0;
    for (ll p : ps) {
        bool ok = true;
        vector<ll> d(n);
        rep(i, n) d[i] = abs(x[i]-p);
        sort(d.begin(), d.end());
        rep(i, n) if (d[i] > l[i]) ok = false;
        if (ok) ans += p-pre;
        pre = p;
    }
    
    cout << ans << '\n';
    
    return 0;
}

T7:Typical Path Problem

注意到,是否存在一条从点 \(A\) 经过点 \(B\) 到达点 \(C\) 的简单路径可以被拆解成是否存在一条从点 \(B\) 到达点 \(A\) 的简单路径以及一条从点 \(B\) 到达点 \(C\) 的简单路径

为了保证每个点只经过一次,所以需要拆点,点 \(V\) 作为流入点,还需增加一个流出点 \(V'\)。具体地,对每个点 \(V\) 到点 \(V'\) 连一条容量为 \(1\) 的边。

对于原图上的每条边 \((U, V)\), 需要对点 \(U'\) 到点 \(V\) 连一条容量为 \(1\) 的边,同时还需对点 \(V'\) 到点 \(U\) 连一条容量为 \(1\) 的边

还需建立一个汇点 \(T\),对点 \(A, C\) 到点 \(T\) 连一条容量为 \(1\) 的边

最后只需从源点 \(B'\) 到汇点 \(T\) 跑一遍最大流验证结果是否是 \(2\) 即可。

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

using namespace std;

int main() {
    int n, m;
    cin >> n >> m;
    int a, b, c;
    cin >> a >> b >> c;
    --a; --b; --c;
    
    int tv = n+n;
    mf_graph<int> g(tv+1);
    rep(i, n) g.add_edge(i, n+i, 1);
    rep(i, m) {
        int u, v;
        cin >> u >> v;
        --u; --v;
        g.add_edge(n+u, v, 1);
        g.add_edge(n+v, u, 1);
    }
    g.add_edge(n+a, tv, 1);
    g.add_edge(n+c, tv, 1);
    
    if (g.flow(n+b, tv) == 2) puts("Yes");
    else puts("No");
    
    return 0;
}