T1: 子集和(四)

可以先求出由这 \(n\) 个数组成 \(t\) 的方案数,这是简单的完全背包问题
dp[t] 表示组成 \(t\) 的方案数

不选择 \(a_i\) 的前提下组成 \(t\) 的方案数也就是把 \(dp[t]\) 减掉至少包含一个 \(a_i\) 的方案

回想一下:\(dp[t-a_i] \to dp[t]\)

所以每个问题的答案就是 \(dp[t] - dp[t-a_i]\)

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

using std::cin;
using std::cout;
using std::vector;
using std::istream;
using std::ostream;
using ll = long long;

//const int mod = 998244353;
const int mod = 1000000007;
struct mint {
    ll x;
    mint(ll x=0):x((x%mod+mod)%mod) {}
    mint operator-() const {
        return mint(-x);
    }
    mint& operator+=(const mint a) {
        if ((x += a.x) >= mod) x -= mod;
        return *this;
    }
    mint& operator-=(const mint a) {
        if ((x += mod-a.x) >= mod) x -= mod;
        return *this;
    }
    mint& operator*=(const mint a) {
        (x *= a.x) %= mod;
        return *this;
    }
    mint operator+(const mint a) const {
        return mint(*this) += a;
    }
    mint operator-(const mint a) const {
        return mint(*this) -= a;
    }
    mint operator*(const mint a) const {
        return mint(*this) *= a;
    }
    mint pow(ll t) const {
        if (!t) return 1;
        mint a = pow(t>>1);
        a *= a;
        if (t&1) a *= *this;
        return a;
    }

    // for prime mod
    mint inv() const {
        return pow(mod-2);
    }
    mint& operator/=(const mint a) {
        return *this *= a.inv();
    }
    mint operator/(const mint a) const {
        return mint(*this) /= a;
    }
};
istream& operator>>(istream& is, mint& a) {
    return is >> a.x;
}
ostream& operator<<(ostream& os, const mint& a) {
    return os << a.x;
}

mint dp[10005];

int main() {
    int n, t;
    cin >> n >> t;
    
    vector<int> a(n);
    rep(i, n) cin >> a[i];
    
    dp[0] = 1;
    rep(i, n) {
        for (int j = 0; j <= t-a[i]; ++j) {
            dp[j+a[i]] += dp[j];
        }
    }
    
    // 考虑减掉至少包含一个 a[i] 的方案
    rep(i, n) {
        if (t >= a[i]) {
            dp[t] -= dp[t-a[i]];
        }
        cout << dp[t] << '\n';
        if (t >= a[i]) {
            dp[t] += dp[t-a[i]];
        }
    }
    
    return 0;
}

T2:路径问题

可以考虑倍增法

jump[i][j] 表示从点 \(i\) 出发,走 \(2^j\) 条边后到达的点

转移式:

\[jump[i][j] = jump[jump[i][j-1]][j-1] \]

mx[i][j] 表示从点 \(i\) 出发,经过 \(2^j\) 条边的路径的边权最大值

转移式:

\( mx[i][j] = \max(mx[i][j-1], mx[jump[i][j-1]][j-1]) \)

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

using std::cin;
using std::cout;
using std::max;
using std::vector;

const int MX = 500005;

int jump[MX][21];
int mx[MX][21];

inline void chmax(int& x, int y) { if (x < y) x = y; }

int main() {
    int n, k;
    cin >> n >> k;
    
    rep(i, n) cin >> jump[i][0];
    rep(i, n) cin >> mx[i][0];
    
    rep(j, 20)rep(i, n) {
        jump[i][j] = jump[jump[i][j-1]][j-1];
        mx[i][j] = max(mx[i][j-1], mx[jump[i][j-1]][j-1]);
    }
    
    rep(i, n) {
        int v = i, ans = 0;
        for (int j = 0; j <= 20; ++j) {
            if (k>>j&1) {
                chmax(ans, mx[v][j]);
                v = jump[v][j];
            }
        }
        cout << ans << '\n';
    }
    
    return 0;
}

T3:航海探险

可以考虑用带权并查集来维护

dist[i] 表示点 \(i\) 到其祖先节点的距离

需要将每个点拆成两个点,也就是 \(x\) 方向和 \(y\) 方向

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

using std::cin;
using std::cout;
using std::min;
using std::vector;

const int MX = 1e5+5;

int fa[MX];
int dist[MX];

int find(int x) {
    if (fa[x] == x) return x;
    int fx = find(fa[x]);
    dist[x] += dist[fa[x]];
    return fa[x] = fx;
}

inline void connect(int a, int b, int d) {
    int ra = find(a), rb = find(b);
    if (ra == rb) return;
    dist[ra] = d-dist[a]+dist[b];
    fa[ra] = rb;
}

int main() {
    int n, m;
    cin >> n >> m;
    
    // i x 方向
    // i+n y 方向
    rep(i, n*2) fa[i] = i;
    
    while (m--) {
        char type;
        cin >> type;
        switch (type) {
            case 'E': { 
                int a, b, e;
                cin >> a >> b >> e;
                connect(a, b, e);
                connect(a+n, b+n, 0);
                break;
            }
            case 'S': {
                int a, b, e;
                cin >> a >> b >> e;
                connect(a+n, b+n, e);
                connect(a, b, 0);
                break;
            }
            case 'W': { 
                int a, b, e;
                cin >> a >> b >> e;
                connect(b, a, e);
                connect(a+n, b+n, 0);
                break;
            }
            case 'N': {
                int a, b, e;
                cin >> a >> b >> e;
                connect(b+n, a+n, e);
                connect(a, b, 0);
                break;
            }
            case '?': {
                int a, b;
                cin >> a >> b;
                if (find(a) == find(b) and find(a+n) == find(b+n)) {
                    cout << abs(dist[a]-dist[b])+abs(dist[a+n]-dist[b+n]) << '\n';
                }
                else puts("?");
                break;
            }
        }
    }
    
    return 0;
}

T4 : 没有考试的天数(二)

\(1 \sim t\)\(a_i (1 \leqslant i \leqslant n)\) 的倍数都去除便是答案

可以考虑容斥原理:

\( \begin{aligned} |\bigcup_{i = 1}^{n} A_i| & = |A_1 \cup A_2 \cup \cdots A_n| \\ &= \sum_{i = 1}^n |A_i| - \sum_{1 \leqslant i < j \leqslant n} |A_i \cap A_j|+ \sum_{1 \leqslant i < j < k \leqslant n} |A_i \cap A_j \cap A_k| + \cdots + (-1)^{n-1} |A_1 \cap A_2 \cap \cdots \cap A_n |\\ &= \sum_{k=1}^{n} (-1)^{k-1} \sum_{1 \leqslant i_1 < i_2 < \cdots < i_k \leqslant n} |A_{i_1} \cap A_{i_2} \cap \cdots \cap A_{i_k}| \end{aligned} \)

\(1 \sim t\) 中是 \(a_i\) 的倍数的数有 \(\lfloor\frac{t}{a_i}\rfloor\)
\(1 \sim t\) 中既是 \(a_i\) 的倍数又是 \(a_j\) 的倍数的数有 \(\lfloor\frac{t}{\operatorname{lcm}(a_i\, , \, a_j)}\rfloor\)
\(\quad \vdots\)
\(1 \sim t\) 中是 \(\{a_{x_1}, a_{x_2}, \cdots, a_{x_k}\}\) 中每个数的倍数的数有 \(\lfloor\frac{t}{\operatorname{lcm}(a_{x_1}\, , \, a_{x_2}\, , \,\cdots\, , \, a_{x_k})}\rfloor\)

代码实现
#pragma GCC optimize ("O2")
#pragma GCC optimize ("unroll-loops")
#pragma GCC target ("avx2"
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using std::cin;
using std::cout;
using std::lcm;
using std::vector;
using ll = long long;

int main() {
    cin.tie(nullptr) -> sync_with_stdio(false);
    
    int n; ll t;
    cin >> n >> t;
    
    vector<ll> a(n);
    rep(i, n) cin >> a[i];
    
    ll ans = t;
    for (int i = 1; i < 1<<n; ++i) {
        ll l = 1; 
        rep(j, n) if (i>>j&1) {
            if (lcm(l, a[j]) > t) {
                l = -1;
                break;
            }
            l = lcm(l, a[j]);
        }
        if (l != -1) { // 注意一定要特判,不然会超时
            if (__builtin_parity(i)) ans -= t/l;
            else ans += t/l;
        }
    }
    
    cout << ans << '\n';
    
    return 0;
}