C. Sum of Min Query

差分更新
原本需要计算 \(N\) 个数的和,但每次查询时只会改变 \(\min(A_{X_i}, B_{X_i})\) 这一个值,所以只要检查这个值的变化情况,就能以 \(O(1)\) 时间处理每个查询!

代码实现
#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, q;
    cin >> n >> q;
    
    vector<int> a(n), b(n);
    rep(i, n) cin >> a[i];
    rep(i, n) cin >> b[i];
    
    ll ans = 0;
    rep(i, n) ans += min(a[i], b[i]);
    
    rep(qi, q) {
        char c; int x, v;
        cin >> c >> x >> v;
        --x;
        
        ans -= min(a[x], b[x]);
        if (c == 'A') a[x] = v; else b[x] = v;
        ans += min(a[x], b[x]);
        
        cout << ans << '\n';
    }
    
    return 0;
}

D. Toggle Maze

拆点+BFS(分层图)
我们需要考虑两种状态世界——开关被按偶数次的世界和开关被按奇数次的世界,并在它们之间来回切换。

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

using namespace std;

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

int dist[505][505][2];

int main() {
    int h, w;
    cin >> h >> w;
    
    vector<string> a(h);
    rep(i, h) cin >> a[i];
    
    const int INF = 1001001001;
    rep(i, h)rep(j, w)rep(k, 2) dist[i][j][k] = INF;
    queue<tuple<int, int, int>> q;
    auto push = [&](int i, int j, int k, int d) {
        if (dist[i][j][k] != INF) return;
        dist[i][j][k] = d;
        q.emplace(i, j, k);
    };
    
    rep(i, h)rep(j, w) if (a[i][j] == 'S') push(i, j, 0, 0);
    while (q.size()) {
        auto [i, j, k] = q.front(); q.pop();
        int d = dist[i][j][k];
        if (a[i][j] == 'G') {
            cout << d << '\n';
            return 0;
        }
        rep(v, 4) {
            int ni = i+di[v], nj = j+dj[v];
            if (ni < 0 or nj < 0 or ni >= h or nj >= w) continue;
            if (a[ni][nj] == '#') continue;
            if (k == 0 and a[ni][nj] == 'x') continue;
            if (k == 1 and a[ni][nj] == 'o') continue;
            int nk = k;
            if (a[ni][nj] == '?') nk ^= 1;
            push(ni, nj, nk, d+1);
        }
    }
    
    puts("-1");
    
    return 0;
}

E. Reachability Query

并查集
只需额外维护每个连通分量中黑色顶点的数量即可!

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

using namespace std;

int main() {
    int n, q;
    cin >> n >> q;
    
    dsu uf(n);
    vector<int> col(n), cnt(n);
    rep(qi, q) {
        int type, v;
        cin >> type >> v;
        --v;
        if (type == 1) {
            int u;
            cin >> u;
            --u;
            u = uf.leader(u); v = uf.leader(v);
            if (u == v) continue;
            int black = cnt[u]+cnt[v];
            uf.merge(u, v);
            cnt[uf.leader(u)] = black;
        }
        else if (type == 2) {
            cnt[uf.leader(v)] -= col[v];
            col[v] ^= 1;
            cnt[uf.leader(v)] += col[v];
        }
        else {
            v = uf.leader(v);
            if (cnt[v]) puts("Yes");
            else puts("No");
        }
    }
    
    return 0;
}

F. kirinuki

按「行」枚举矩形的 底边(把当前遍历的行看作矩形的底行)。对于固定底行 bi

  • 先计算每列的高度 a[j],即以当前行为底向上连续 '.' 的最大高度(遇到 # 就置 0)。
  • 那么,任一以当前行为底、竖向高度为 t1 ≤ t ≤ a[j])的、横跨某一列区间 [ly,ry] 的矩形是全 '.' 的,当且仅当该区间的 最小高度 ≥ t
  • 因此问题可等价为:对当前高度数组 a[0..m-1],枚举所有区间 [L,R],并对能选用的高度 t = 1..min_{j∈[L,R]} a[j] 统计满足 t * width ≤ K 的高度数(这里 width = R-L+1)。把这些对每一个底行累加起来就是最终答案。

关键难点是:要高效地对同一个底行、对所有区间进行计数(不能枚举所有 \(O(M^2)\) 区间)。使用笛卡尔树把所有区间按“区间最小值所在位置”进行一次性唯一分配,从而把复杂度降下来。

如何在单个节点 v 上统计贡献

固定节点 v,设其高度 h = a[v],在它负责的左右范围内共有 A = left span = v - L 种向左扩展长度(取 1..A)和 B = right span = R - v 向右扩展长度(取 1..B)。任意选 a(左扩展)和 b(右扩展),宽 w = a + b - 1,该区间的最小值就是在 v,且允许的高度 t 可从 1h。但我们还要满足面积约束 t * w ≤ K

对于固定高度 t,允许的最大宽是 W(t) = floor(K / t)。于是固定 t,被 v 负责并且满足面积约束的区间数等于:

count_t = # of pairs (a,b) with 1≤a≤A, 1≤b≤B, a+b-1 ≤ W(t)

p=a-1, q=b-1 变换为非负整数,就变成:

#(p,q ≥ 0, p ≤ A-1, q ≤ B-1, p+q ≤ W(t)-1).

无上界时 #(p+q ≤ S) 的值等于 tri(S) = (S+2)(S+1)/2(若 S>=0,否则 0)。按有限上界用常规的容斥,可以得到:

count_t = tri(F)
          - tri(F - A)
          - tri(F - B)
          + tri(F - A - B)

(画个图就能看出来了)

其中 F = W(t)-1 = floor(K/t) -1,当 tri 的参数为负时定义为 0。

于是节点 v 的总贡献为对 t = 1..h 把上式相加。把 t 的求和与 tri(...)F(=floor(K/t)-1)结合后,先把

f[h][c] = sum_{t=1..h} tri( floor(K/t) - 1 - c )

预计算好(c 是容斥时需要移动的常量)。然后节点 v 的贡献用一次常数时间的组合:

contrib(v) = sum_{t=1..h} [ tri(F) - tri(F-A) - tri(F-B) + tri(F-A-B) ]
           = f[h][0] - f[h][A] - f[h][B] + f[h][A+B]

这样就把原本对每个 t 的重复循环通过预处理 f 合并成了 \(O(1)\) 的操作。

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

using namespace std;
using ll = long long;

template<class T=long long>
struct CartesianTree {
    int n, root;
    vector<int> l, r;
    CartesianTree() {}
    CartesianTree(const vector<T>& a, bool _max=true) {
        n = a.size();
        l = r = vector<int>(n, -1);
        vector<int> st;
        rep(i, n) {
            int p = -1;
            while (st.size() and !((a[st.back()] < a[i]) ^ _max)) { 
                int j = st.back(); st.pop_back();
                r[j] = p; p = j;
            }
            l[i] = p;
            st.push_back(i);
        }
        rep(i, st.size()-1) r[st[i]] = st[i+1];
        root = st[0];
    }
};

int main() {
    int n, m, k;
    cin >> n >> m >> k;
    
    vector<string> s(n);
    rep(i, n) cin >> s[i];
    
    ll ans = 0;
    vector f(n+1, vector<ll>(m+2));
    auto tri = [&](ll s) -> ll {
        if (s < 0) return 0;
        return (s+2)*(s+1)/2;
    };
    for (int h = 1; h <= n; ++h)rep(c, m+2) {
        f[h][c] = f[h-1][c] + tri(k/h-1-c);
    }
    auto add = [&](int l, int r, int h) {
        l++; r++;
        ans += f[h][0];
        ans -= f[h][l];
        ans -= f[h][r];
        ans += f[h][l+r];
    };
    
    vector<int> a(m);
    rep(bi, n) {
        rep(i, m) {
            a[i]++;
            if (s[bi][i] == '#') a[i] = 0;
        }
        CartesianTree t(a, false);
        auto dfs = [&](auto& f, int l, int r, int v) -> void {
            if (v == -1) return;
            add(v-l, r-v, a[v]);
            f(f, l, v-1, t.l[v]);
            f(f, v+1, r, t.r[v]);
        };
        dfs(dfs, 0, m-1, t.root);
    }
    
    cout << ans << '\n';
    
    return 0;
}

G. sqrt(n²+n+X)

假设 \(n^2+n+X=m^2\)

\((n+\frac{1}{2})^2 - \frac{1}{4} + X = m^2\)

\((2n+1)^2 - 1 + 4X = 4m^2\)

\((2m)^2 - (2n+1)^2 = 4X-1\)

\(((2m)+(2n+1))((2m)-(2n+1)) = 4X-1\)

假设 \(a=(2m)+(2n+1)\)\(b=(2m)-(2n+1)\)
\((2n+1)+(2n+1) = a-b\)
\(4n=a-b-2\)
\(n=\frac{a-b-2}{4}\)

\(a,b\) 都是 \(4X-1\) 的因子,那么我们只需枚举 \(4X-1\) 的成对的因子即可!

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

using namespace std;
using ll = long long;

int main() {
    ll x;
    cin >> x;
    x = 4*x-1;
    
    set<ll> ans;
    for (ll i = 1; i*i <= abs(x); ++i) {
        if (x%i) continue;
        ll a = i, b = x/i;
        rep(sgn, 2) {
            rep(swp, 2) {
                if ((a-b-2)%4 == 0) {
                    ll n = (a-b-2)/4;
                    ans.insert(n);
                }
                swap(a, b);
            }
            a = -a; b = -b;
        }
    }
    
    cout << ans.size() << '\n';
    for (ll x : ans) cout << x << ' ';
    
    return 0;
}