C. 2x2 Placing

set<pair<int, int>>

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

using namespace std;
using P = pair<int, int>;

int main() {
    int n, m;
    cin >> n >> m;
    
    int ans = 0;
    set<P> st;
    rep(i, m) {
        int r, c;
        cin >> r >> c;
        bool ok = true;
        rep(dr, 2)rep(dc, 2) {
            if (st.count(P(r+dr, c+dc))) ok = false;
        }
        if (!ok) continue;
        rep(dr, 2)rep(dc, 2) {
            st.emplace(r+dr, c+dc);
        }
        ans++;
    }
    
    cout << ans << '\n';
    
    return 0;
}

D. Teleport Maze

暴力 \(\operatorname{bfs}\) 的复杂度为 \(O(H^2W^2)\)

考虑优化:

注意到,使用同一个字母的传送器两次显然是没用的,所以如果你使用了一次传送器,接下来你就不能再使用那个字母了!
时间复杂度可以降为 \(O(HW)\)

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

using namespace std;
using P = pair<int, int>;

const int di[] = {-1, 0, 1, 0};
const int dj[] = {0, 1, 0, -1};

int main() {
    int h, w;
    cin >> h >> w;
    
    vector<string> s(h);
    rep(i, h) cin >> s[i];
    
    vector<vector<P>> warps(256);
    rep(i, h)rep(j, w) warps[s[i][j]].emplace_back(i, j);
    
    const int INF = 1001001001;
    vector dist(h, vector<int>(w, INF));
    queue<P> q;
    auto push = [&](int i, int j, int d) {
        if (dist[i][j] != INF) return;
        dist[i][j] = d;
        q.emplace(i, j);
    };
    push(0, 0, 0);
    
    vector<bool> done(256);
    while (q.size()) {
        auto [i, j] = q.front(); q.pop();
        int d = dist[i][j];
        rep(v, 4) {
            int ni = i+di[v], nj = j+dj[v];
            if (ni < 0 or ni >= h or nj < 0 or nj >= w) continue;
            if (s[ni][nj] == '#') continue;
            push(ni, nj, d+1);
        }
        if (s[i][j] != '.' and !done[s[i][j]]) {
            for (auto [ni, nj] : warps[s[i][j]]) {
                push(ni, nj, d+1);
            }
            done[s[i][j]] = true;
        }
    }
    
    int ans = dist[h-1][w-1];
    if (ans == INF) ans = -1;
    cout << ans << '\n';
    
    return 0;
}

E. Minimum Swap

考虑排列的环分解
单次交换操作的效果:

  • 当交换元素属于不同环时 \(\to\) 合并两个环
  • 当交换元素属于同一个环时 \(\to\) 将该环分裂为两个

最终是要变成 \(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<int> p(n);
    rep(i, n) cin >> p[i], p[i]--;
    
    ll ans = 0;
    vector<bool> used(n);
    rep(i, n) if (!used[i]) {
        int v = i, c = 0;
        while (!used[v]) {
            used[v] = true;
            v = p[v];
            c++;
        }
        ans += ll(c-1)*c/2;
    }
    
    cout << ans << '\n';
    
    return 0;
}

F. Starry Landscape Photo

枚举 \(b=1, 2, \cdots, N\),统计包含第 \(b\) 亮的这个星星的位置 \(X_b\) 的区间数
对于这个区间数,可以用树状数组或平板电视来找到 \(X_b\) 的左右两边亮度排名不超过 \(b\) 的星星个数,用乘法原理算一下贡献即可

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

using namespace std;
using namespace __gnu_pbds;
using ll = long long;

template<class T>
using Tree = tree<T, null_type, less<T>, rb_tree_tag, tree_order_statistics_node_update>;

int main() {
    int n;
    cin >> n;
    
    vector<int> b(n);
    rep(i, n) cin >> b[i], b[i]--;
    
    vector<int> x(n);
    rep(i, n) x[b[i]] = i;
    
    ll ans = 0;
    Tree<int> t;
    rep(i, n) {
        int l = t.order_of_key(x[i]), r = i-l;
        ans += ll(l+1)*(r+1);
        t.insert(x[i]);
    }
    
    cout << ans << '\n';
    
    return 0;
}

G. Linear Inequation

数位 \(\text{dp}\) 或生成函数
可以参考abc300ex的官方题解

数位 \(\text{dp}\)

把每个 \(x_i\) 按二进制位分解,把“每一位上哪些 \(i\)\(1\)”视作一次 \(\text{0/1}\) 子集和问题(同一位的分布对所有位都一样),再用按位合并(除以 \(2\) 并根据 \(m\) 的当前位做奇偶调整)来模拟进位和保持 \(\leqslant M\) 的限制,从而在多位上递推计数。

代码实现
#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 mint = modint998244353;

int main() {
    int n; ll m;
    cin >> n >> m;
    
    vector<int> a(n);
    rep(i, n) cin >> a[i];
    
    const int X = 20000;
    vector<mint> dp(X+1);
    dp[0] = 1;
    
    while (m) {
        rep(i, n) {
            for (int j = X; j >= a[i]; --j) {
                dp[j] += dp[j-a[i]];
            }
        }
        vector<mint> old(X+1);
        swap(dp, old);
        rep(j, X+1) {
            int nj = j;
            if ((nj&1) != (m&1)) nj++;
            dp[nj/2] += old[j];
        }
        
        m >>= 1;
    }
    
    cout << dp[0].val() << '\n';
    
    return 0;
}

生成函数:

考虑在序列 \(A\) 的开头添加一个元素 \(A_0=1\)
这样原问题就转化成了统计满足以下条件的长度为 \(N+1\) 的序列 \(x = (x_0, x_1, \cdots, x_n)\) 的序列个数:

  • \(\displaystyle\sum_{i=0}^N A_ix_i = M\)

考虑使用生成函数
\(f_i(x) = x^0 + x^{A_i} + x^{2A_i} + \cdots\)
那么最后的答案就是 \([x^M] \prod f_i\)

\(f_i(x) = \frac{1}{1-x^{A_i}}\)
\(\Rightarrow \prod f_i = \dfrac{1}{\prod\limits_{i=0}^N (1-x^{A_i})}\)

注意到这里的 \(M\) 很大,所以只能用 \(\text{Bostan-Mori}\) 求解
时间复杂度是 \(O(N\log N \log M)\)

代码实现
#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 mint = modint998244353;
using fps = vector<mint>;

mint bostan_mori(fps p, fps q, ll n) {
    while (n) {
        fps iq = q;
        for (int i = 1; i < q.size(); i += 2) iq[i] = -iq[i];
        p = convolution(p, iq);
        iq = convolution(q, iq);
        rep(i, q.size()) q[i] = iq[i*2];
        for (int i = n&1; i < p.size(); i += 2) p[i/2] = p[i];
        p.resize((p.size()+!(n&1))/2);
        n >>= 1;
    }
    return p[0] / q[0];
}

int main() {
    int n; ll m;
    cin >> n >> m;
    
    fps p = {1}, q = {1, -1};
    rep(i, n) {
        int a;
        cin >> a;
        fps f(a+1);
        f[0] = 1; f[a] = -1;
        q = convolution(q, f);
    }
    
    mint ans = bostan_mori(p, q, m);
    cout << ans.val() << '\n';
    
    return 0;
}