ABC149F. Surrounded Nodes

ABC239Ex. Dice Product 2

dp[i] 表示由 \(M\) 通过除以若干个整数 \(x\) 变成 \(i\) 的期望次数

转移方程:\(dp[i] = 1 + \sum\limits_{j=1}^n dp[\lfloor\frac{i}{j}\rfloor] \Leftrightarrow dp[i] = \frac{N}{N-1}(1 + \sum\limits_{j=2}^n dp[\lfloor\frac{i}{j}\rfloor])\)

时间复杂度为 \(\mathcal{O}(NM)\)

注意到和 \(\lfloor\frac{i}{j}\rfloor\) 一样的部分可以用整数分块来优化
用记忆化搜索实现,复杂度同杜教筛,为 \(O(m^{\frac{3}{4}})\)

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

int main() {
    int n, m;
    cin >> n >> m;
    
    mint inv_n1 = mint(n-1).inv();
    mint cost = mint(n)/(n-1);
    
    unordered_map<int, mint> dp;
    auto f = [&](auto& f, int x) -> mint {
        if (x == 0) return 0;
        if (dp.count(x)) return dp[x];
        mint res;
        for (int i = n; i > 1;) {
            int a = x/i;
            int ni = x/(a+1);
            res += f(f, a)*(i-ni);
            i = ni;
        }
        res *= inv_n1; res += cost;
        return dp[x] = res;
    };
    
    mint ans = f(f, m);
    cout << ans.val() << '\n';
    
    return 0;
}

ABC265G. 012 Inversion

延迟线段树

每个点需要维护以下信息:

  • \(0/1/2\) 的个数
  • 有序对 \((0, 0)\), \((0, 1)\), \((0, 2)\)\((1, 0)\)\((1, 1)\)\((1, 2)\)\((2, 0)\)\((2, 1)\)\((2, 2)\) 的个数

对于操作二其实就是延迟线段树里的映射 \(F\)

代码实现
#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;
using ll = long long;

struct S {
    array<int, 3> c;
    array<array<ll, 3>, 3> d;
    S() {
        fill(c.begin(), c.end(), 0);
        rep(i, 3)rep(j, 3) d[i][j] = 0;
    }
};
S op(S a, S b) {
    rep(i, 3)rep(j, 3) {
        a.d[i][j] += b.d[i][j];
        a.d[i][j] += (ll)a.c[i]*b.c[j];
    }
    rep(i, 3) a.c[i] += b.c[i];
    return a;
}
S e() { return S(); }

struct F {
    array<int, 3> a;
    F(): a({0, 1, 2}) {}
};
S mapping(F f, S x) {
    S res;
    rep(i, 3)rep(j, 3) {
        res.d[f.a[i]][f.a[j]] += x.d[i][j];
    }
    rep(i, 3) res.c[f.a[i]] += x.c[i];
    return res;
}
F comp(F f2, F f1) {
    rep(i, 3) f1.a[i] = f2.a[f1.a[i]];
    return f1;
}
F id() { return F(); }

int main() {
    cin.tie(nullptr) -> sync_with_stdio(false);
    
    int n, q;
    cin >> n >> q;
    
    vector<int> a(n);
    rep(i, n) cin >> a[i];
    
    lazy_segtree<S, op, e, F, mapping, comp, id> t(n);
    rep(i, n) {
        S s;
        s.c[a[i]] = 1;
        t.set(i, s);
    }
    
    rep(qi, q) {
        int type, l, r;
        cin >> type >> l >> r;
        --l;
        if (type == 1) {
            S s = t.prod(l, r);
            ll ans = s.d[1][0] + s.d[2][0] + s.d[2][1];
            cout << ans << '\n';
        }
        else {
            F f;
            rep(i, 3) cin >> f.a[i];
            t.apply(l, r, f);
        }
    }
    
    return 0;
}

ABC223H. Xor Query

前缀线性基的板题
对于每一位上的基底,尽可能挂越靠右的数

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

using namespace std;
using ll = long long;

const int D = 60;
template<typename T>
struct MaxBasis {
    vector<T> d;
    vector<int> w;
    MaxBasis(): d(D), w(D) {}
    void add(T x, int nw) {
        rep(i, D) if (x>>i&1) {
            if (d[i]) {
                if (nw > w[i]) swap(d[i], x), swap(w[i], nw);
                x ^= d[i];
            }
            else {
                d[i] = x;
                w[i] = nw;
                break;
            }
        }
    }
    bool solve(int l, ll x) {
        rep(i, D) if (x>>i&1) {
            if (w[i] >= l) x ^= d[i];
        }
        return x == 0;
    }
};

struct Q {
    int i, l; ll x;
    Q(int i, int l, ll x): i(i), l(l), x(x) {}
};

int main() {
    cin.tie(nullptr) -> sync_with_stdio(false);
    
    int n, q;
    cin >> n >> q;
    
    vector<ll> a(n);
    rep(i, n) cin >> a[i];
   
    vector<vector<Q>> qs(n);
    rep(qi, q) {
        int l, r; ll x;
        cin >> l >> r >> x;
        --l; --r;
        qs[r].emplace_back(qi, l, x);
    }
    
    MaxBasis<ll> mb;
    vector<bool> ans(q);
    rep(i, n) {
        mb.add(a[i], i);
        for (auto [qi, l, x] : qs[i]) {
            ans[qi] = mb.solve(l, x);
        }
    }
    
    rep(i, q) {
        if (ans[i]) puts("Yes");
        else puts("No");
    }
    
    return 0;
}

ABC233F. Swap and Sort

原题其实就是给定一张 \(N\) 个点 \(M\) 条边的图,顶点 \(i\) 上放有棋子 \(P_i\) 。一条边直接相连的两端点可以交换其上放置的棋子。问是否能让棋子 \(p_i\) 落在点 \(i\) 上。对于树的情况比较简单,可以从叶子节点 \(V\) 开始搜,在整棵树上找是否有哪个点上放有棋子 \(v\),如果找到了的话,那么这个叶节点及其和它父亲相连的边就可以不考虑了。那么对于一般图的话,只需考虑每个连通块的生成树即可。

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

struct Edge {
    int to, id;
    Edge(int to, int id): to(to), id(id) {}
};

int main() {
    int n;
    cin >> n;
    
    vector<int> P(n);
    rep(i, n) cin >> P[i];
    rep(i, n) P[i]--;
    
    int m;
    cin >> m;
    dsu uf(n);
    vector<vector<Edge>> g(n);
    rep(i, m) {
        int a, b;
        cin >> a >> b;
        --a; --b;
        if (uf.same(a, b)) continue;
        uf.merge(a, b);
        g[a].emplace_back(b, i+1);
        g[b].emplace_back(a, i+1);
    }
    
    vector<int> ans;
    rep(sv, n) if (uf.leader(sv) == sv) {
        auto get = [&](auto& f, int v, int tg, int p=-1) -> bool {
            if (P[v] == tg) return true;
            for (auto [u, id] : g[v]) {
                if (u == p) continue;
                if (f(f, u, tg, v)) {
                    ans.push_back(id);
                    swap(P[v], P[u]);
                    return true;
                }
            }
            return false;
        }; 
        auto dfs = [&](auto& f, int v, int p=-1) -> void {
            for (auto [u, id] : g[v]) {
                if (u == p) continue;
                f(f, u, v);
            }
            if (!get(get, v, v)) {
                puts("-1");
                exit(0);
            }
        };
        dfs(dfs, sv);
    }
    
    cout << ans.size() << '\n';
    for (int i : ans) cout << i << ' ';
    
    return 0;
}

ABC233G. Strongest Takahashi

注意到如果能用一个子矩阵,用两个相交的子矩阵反而更劣
考虑二维区间dp
dp[si][sj][ti][tj] 表示摧毁左上坐标为 \((s_i, s_j)\) 以及右下坐标为 \((t_i-1, t_j-1)\) 的子矩阵中的所有障碍物的最小代价

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

using namespace std;

inline void chmin(int& x, int y) { if (x > y) x = y; }

int dp[55][55][55][55];

int main() {
    int n;
    cin >> n;
    
    vector<string> s(n);
    rep(i, n) cin >> s[i];
    
    rep(ti, n+1)rep(si, ti)rep(tj, n+1)rep(sj, tj) {
        dp[si][sj][ti][tj] = max(ti-si, tj-sj);
    }
    rep(i, n)rep(j, n) if (s[i][j] == '.') {
        dp[i][j][i+1][j+1] = 0;
    }
    
    for (int wi = 1; wi <= n; ++wi) {
        for (int wj = 1; wj <= n; ++wj) {
            rep(si, n)rep(sj, n) {
                int ti = si+wi, tj = sj+wj;
                if (ti > n) break;
                if (tj > n) break;
                for (int k = si+1; k < ti; ++k) {
                    int now = dp[si][sj][k][tj];
                    now += dp[k][sj][ti][tj];
                    chmin(dp[si][sj][ti][tj], now);
                }
                for (int k = sj+1; k < tj; ++k) {
                    int now = dp[si][sj][ti][k];
                    now += dp[si][k][ti][tj];
                    chmin(dp[si][sj][ti][tj], now);
                }
            }
        }
    }
    
    cout << dp[0][0][n][n] << '\n';
    
    return 0;
}

ABC233H. Manhattan Christmas Tree

二分答案
先将坐标系顺时针旋转45°,得到切比雪夫距离,接下来就是经典的二维数点问题了

代码实现
#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;
using P = pair<int, int>;

const int M = 100005;
const int MX = M*2;

int main() {
    int n;
    cin >> n;
    
    vector<vector<int>> ps(MX);
    rep(i, n) {
        int x, y;
        cin >> x >> y;
        ps[x+y].push_back(x-y+M);
    }
    
    int q;
    cin >> q;
    vector<int> a(q), b(q), k(q);
    rep(i, q) {
        int x, y;
        cin >> x >> y >> k[i];
        a[i] = x+y;
        b[i] = x-y+M;
    }
    
    vector<int> wa(q, -1), ac(q, MX);
    rep(ti, 18) {
        vector<int> num(q), wj(q);
        vector<vector<int>> qs(MX);
        rep(i, q) {
            wj[i] = (wa[i]+ac[i])/2;
            int lx = a[i]-wj[i], rx = a[i]+wj[i]+1;
            lx = max(lx, 0);
            rx = min(rx, MX-1);
            qs[lx].push_back(i);
            qs[rx].push_back(q+i);
        }
        fenwick_tree<int> d(MX);
        rep(x, MX) {
            for (int qi : qs[x]) {
                int i = qi%q;
                int sign = qi < q ? -1 : 1;
                int ly = b[i]-wj[i], ry = b[i]+wj[i]+1;
                ly = max(ly, 0);
                ry = min(ry, MX);
                num[i] += d.sum(ly, ry)*sign;
            }
            for (int y : ps[x]) d.add(y, 1);
        }
        
        rep(i, q) {
            if (num[i] >= k[i]) ac[i] = wj[i];
            else wa[i] = wj[i];
        }
    }
    
    rep(i, q) cout << ac[i] << '\n';
    
    return 0;
}

ABC249F. Ignore Operations

考虑枚举最后一个操作 \(1\),那么前面的操作不管是啥样都无所谓了,我们只需考虑后面的操作,显然应该将后面的所有操作 \(1\) 都跳过,为了使得最终的结果最大,所以应该尽可能跳过操作 \(2\)\(y < 0\) 的操作。可以用小根堆来维护所有 \(y < 0\) 的操作 \(2\),如果当前堆的大小超过剩下的 \(k\),就弹出堆中的最大值。 对于枚举最后一个操作 \(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, k;
    cin >> n >> k;
    
    vector<int> t(n), y(n);
    rep(i, n) cin >> t[i] >> y[i];
    
    ll ans = -1e18;
    ll sum = 0;
    priority_queue<int> q;
    for (int i = n-1; i >= 0; --i) {
        if (k < 0) break;
        if (t[i] == 1) {
            ans = max(ans, y[i]+sum);
            k--;
            if (q.size() > k) {
                sum  += q.top(); q.pop();
            }
        }
        else {
            if (y[i] >= 0) sum += y[i];
            else {
                q.push(y[i]);
                if (q.size() > k) {
                    sum  += q.top(); q.pop();
                }
            }
        }
    }
    if (k >= 0) ans = max(ans, sum);
    
    cout << ans << '\n';
	
	return 0;
}