AGC020C. Median Sum

记原序列的总和为 \(S\)

容易发现如果把空集也考虑进去的话,在左边任取一个子集,其和为 \(x\),那么一定可以在右边找到一个子集满足它的和为 \(S - x\)。也就是说,位于权值为 \(\frac{S}{2}\) 的左右两边的子集是对称的。

于是,我们就能推出我们需要求的中位数就是总和超过 \(\frac{S}{2}\) 的第一个子集,但直接做 01 背包的话复杂度会炸

我们可以使用 bitset 来加速一下即可

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

using namespace std;

const int MX = 2005;
const int M = MX*MX;

int main() {
    int n;
    cin >> n;

    vector<int> a(n);
    rep(i, n) cin >> a[i];

    int s = 0;
    rep(i, n) s += a[i];

    bitset<M> dp;
    dp[0] = 1;
    rep(i, n) dp |= dp<<a[i];

    for (int i = (s+1)/2; i < M; ++i) {
        if (dp[i]) {
            cout << i << '\n';
            return 0;
        }
    }

    return 0;
}

AGC030D. Inversion Sum

线性期望 + 概率转移
把“对所有 \(2^Q\) 种操作方式求最终逆序对数之和”问题转化为先算每一对位置 \(i,j\) 最终成为逆序(即位置 \(i\) 的值大于位置 \(j\) 的值)的期望次数,最后把期望乘以 \(2^Q\) 得到“和”。

dp[i][j] 表示在当前已处理的前 \(t\) 个操作之后,位置 \(i\) 的值严格大于位置 \(j\)概率。初始化时没有随机操作,dp[i][j] 是一个 0/1 值:

dp[i][j] = (a[i] > a[j]) ? 1 : 0;

处理一次可选交换 (x,y) 的转移

对于某个操作对 \((x,y)\),我们有 1/2 概率交换位置 \(x\)\(y\)、1/2 不交换。考虑任意位置 \(i\)

  • 比较 (x,i):如果不交换,概率为老的 dp[x][i];如果交换,位置 \(x\) 上的是原来 \(y\) 的值,比较结果为老的 dp[y][i]。因此新的概率是两者的平均:

    \[dp_{\text{new}}[x][i] = \frac{dp_{\text{old}}[x][i] + dp_{\text{old}}[y][i]}{2}. \]

  • 同理 (i,x)

    \[dp_{\text{new}}[i][x] = \frac{dp_{\text{old}}[i][x] + dp_{\text{old}}[i][y]}{2}. \]

  • 对于 (x,y) 本身:不交换时比较结果仍是 dp[x][y];交换时位置 \(x\)\(y\) 的值对调,比较 \(x>y\) 在交换情况下等价于原来的 dp[y][x]。因此

    \[dp_{\text{new}}[x][y] = \frac{dp_{\text{old}}[x][y] + dp_{\text{old}}[y][x]}{2}. \]

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

int main() {
    int n, q;
    cin >> n >> q;
    
    vector<int> a(n);
    rep(i, n) cin >> a[i];
    
    vector dp(n, vector<mint>(n));
    rep(i, n)rep(j, n) dp[i][j] = a[i] > a[j];
    mint inv2 = mint(2).inv();
    
    rep(qi, q) {
        int x, y;
        cin >> x >> y;
        --x; --y;
        
        vector<mint> lx(n), rx(n);
        vector<mint> ly(n), ry(n);
        rep(i, n) lx[i] = dp[x][i];
        rep(i, n) rx[i] = dp[i][x];
        rep(i, n) ly[i] = dp[y][i];
        rep(i, n) ry[i] = dp[i][y];
        rep(i, n) {
            dp[x][i] = dp[y][i] = (lx[i]+ly[i])*inv2;
            dp[i][x] = dp[i][y] = (rx[i]+ry[i])*inv2;
        }
        dp[x][y] = dp[y][x] = (lx[y]+ly[x])*inv2;
    }
    
    mint ans;
    rep(j, n)rep(i, j) ans += dp[i][j];
    ans *= mint(2).pow(q);
    cout << ans.val() << '\n';
    
    return 0;
}

AGC044B. Joker

按顺序从点 \(P_i\) 出发跑 \(\mathrm{bfs}\)

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

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

inline void chmin(int& a, int b) { if (a > b) a = b; }

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

int main() {
    int n;
    cin >> n;
    
    vector d(n, vector<int>(n));
    rep(i, n)rep(j, n) {
        d[i][j] = i;
        chmin(d[i][j], j);
        chmin(d[i][j], n-1-i);
        chmin(d[i][j], n-1-j);
    }
    vector a(n, vector<int>(n, 1));
    
    int ans = 0;
    rep(ni, n*n) {
        int num;
        cin >> num;
        --num;
        int si = num/n, sj = num%n;
        ans += d[si][sj];
        a[si][sj]--;
        queue<P> q;
        q.emplace(si, sj);
        while (q.size()) {
            auto [i, j] = q.front(); q.pop();
            int nd = d[i][j]+a[i][j];
            rep(v, 4) {
                int ni = i+di[v], nj = j+dj[v];
                if (ni < 0 or nj < 0 or ni >= n or nj >= n) continue;
                if (d[ni][nj] <= nd) continue;
                d[ni][nj] = nd;
                q.emplace(ni, nj);
            }
        }
    }
    
    cout << ans << '\n';
    
    return 0;
}

ARC101. Median of Medians

二分答案
假设答案为 \(x\)
求满足中位数 \(\geqslant x\) 的区间数至少有 \(\left\lceil \dfrac{\frac{n(n+1)}{2}}{2} \right\rceil\) 个的 \(x\) 的最大值

其中求“中位数 \(\geqslant x\) 的区间数”就是 P3031

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

int main() {
    int n;
    cin >> n;
    
    vector<int> a(n);
    rep(i, n) cin >> a[i];
    
    int ac = 0, wa = 1001001001;
    while (abs(ac-wa) > 1) {
        int wj = (ac+wa)/2;
        
        auto ok = [&]{
            ll cnt = 0;
            fenwick_tree<int> t(2*n+1);
            int x = 0;
            t.add(n, 1);
            rep(i, n) {
                if (a[i] >= wj) x++;
                else x--;
                cnt += t.sum(0, n+x+1);
                t.add(n+x, 1);
            }
            return cnt*2 >= n*ll(n+1)/2;
        }();
        
        (ok ? ac : wa) = wj;
    }
    
    cout << ac << '\n';
    
    return 0;
}

ARC117C. Tricolor Pyramid

打表发现是 \((-a-b) \bmod 3\),所以只要计算组合数 \(\bmod 3\),如果 \(n\)
偶数,再乘上 \(-1\) 即可。
因为这里是小模数,用 \(\mathrm{lucas}\) 算组合数会比较方便。

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

using namespace std;
using ll = long long;

const int mod = 3;
//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;
}

struct modinv {
  int n; vector<mint> d;
  modinv(): n(2), d({0,1}) {}
  mint operator()(int i) {
    while (n <= i) d.push_back(-d[mod%n]*(mod/n)), ++n;
    return d[i];
  }
  mint operator[](int i) const { return d[i];}
} invs;
struct modfact {
  int n; vector<mint> d;
  modfact(): n(2), d({1,1}) {}
  mint operator()(int i) {
    while (n <= i) d.push_back(d.back()*n), ++n;
    return d[i];
  }
  mint operator[](int i) const { return d[i];}
} facts;
struct modfactinv {
  int n; vector<mint> d;
  modfactinv(): n(2), d({1,1}) {}
  mint operator()(int i) {
    while (n <= i) d.push_back(d.back()*invs(n)), ++n;
    return d[i];
  }
  mint operator[](int i) const { return d[i];}
} ifacts;
mint comb(int n, int k) {
  if (n < k || k < 0) return 0;
  return facts(n)*ifacts(k)*ifacts(n-k);
}

mint lucas(int n, int k) {
  if (n == 0 && k == 0) return 1;
  mint res = comb(n%3, k%3);
  return res*lucas(n/3, k/3);
}

int main() {
    int n;
    string s;
    cin >> n >> s;
    
    string t = "BWR";
    mint ans;
    rep(i, n) {
        int x = 0;
        while (t[x] != s[i]) ++x;
        ans += lucas(n-1, i)*x;
    }
    if (n%2 == 0) ans = -ans;
    
    cout << t[ans.x] << '\n';
    
    return 0;
}

ARC160. Power Up

dp[i][j] 对数字 \(1, 2, \cdots, i\) 进行操作,生成 \(j\)\(i+1\)
然后用后缀和优化一下即可

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

int main() {
	int n;
	cin >> n;
	
	const int M = 200055;
	vector<int> c(M);
	rep(i, n) {
	    int a;
	    cin >> a;
	    c[a]++;
	}
	
	vector<mint> dp(1, 1);
	rep(a, M) {
	    int x = c[a];
	    vector<mint> p(((int)dp.size()+x)/2+1);
	    swap(dp, p);
	    rep(i, p.size()) {
	        int nx = x+i;
	        dp[nx/2] += p[i];
	    }
	    for (int i = (int)dp.size()-2; i >= 0; --i) dp[i] += dp[i+1];
	}
	
	mint ans = dp[0];
	cout << ans.val() << '\n';
	
	return 0;
}

ABC058D. ###

\( \sum\limits_{1 \leqslant i<j \leqslant n} (x_j-x_i) \times \sum\limits_{1 \leqslant k<l \leqslant n} (x_l-x_k) \)

只考虑第一项,第二项同理

\( \begin{aligned} \sum\limits_{1 \leqslant i<j \leqslant n} (x_j-x_i) &= \sum_{i=1}^n\sum_{j=i+1}^n (x_j - x_i)\\ &= \sum_{i=1}^n\sum_{j=i+1}^n x_j - \sum_{i=1}^n\sum_{j=i+1}^n x_i\\ &= \sum_{j=2}^n\sum_{i=1}^{j-1} x_j - \sum_{i=1}^n (n-i)x_i\\ &= \sum_{i=2}^n (i-1)x_i - \sum_{i=1}^n (n-i)x_i\\ &= \sum\limits_{i=1}^n (i-1 - (n-i)) x_i \end{aligned} \)

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

mint f(int n) {
    mint res;
    rep(i, n) {
        int x;
        cin >> x;
        res += mint(x)*(i-(n-i-1));
    }
    return res;
}

int main() {
    int n, m;
    cin >> n >> m;
    
    mint x = f(n);
    mint y = f(m);
    
    mint ans = x*y;
    cout << ans.val() << '\n';
    
    return 0;
}

ABC127E. Cell Distance

\( \sum\sum (|x_i-x_j| + |y_i-y_j|) = \sum\sum |x_i - x_j| + \sum\sum |y_i-y_j| \)

所以,\(x\)\(y\) 可以分别单独考虑
这里仅讨论 \(x\) 的贡献,\(y\) 是类似的

\( \begin{aligned} \displaystyle \sum_{\text{所有的放法}}\sum_i\sum_j |x_i - x_j| &= \sum_i\sum_j\sum_{\text{所有的放法}} |x_i - x_j|\\ &= \sum_i\sum_j |x_i-x_j| \times {}_{nm-2}C_{k-2}\\ &= {}_{nm-2}C_{k-2} \sum_i\sum_j |x_i - x_j|\\ &= {}_{nm-2}C_{k-2} \sum_{d=0}^{n-1} d \times (n-d) \times m \times m \end{aligned} \)

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

struct modinv {
  int n; vector<mint> d;
  modinv(): n(2), d({0,1}) {}
  mint operator()(int i) {
    while (n <= i) d.push_back(-d[mint::mod()%n]*(mint::mod()/n)), ++n;
    return d[i];
  }
  mint operator[](int i) const { return d[i];}
} invs;
struct modfact {
  int n; vector<mint> d;
  modfact(): n(2), d({1,1}) {}
  mint operator()(int i) {
    while (n <= i) d.push_back(d.back()*n), ++n;
    return d[i];
  }
  mint operator[](int i) const { return d[i];}
} facts;
struct modfactinv {
  int n; vector<mint> d;
  modfactinv(): n(2), d({1,1}) {}
  mint operator()(int i) {
    while (n <= i) d.push_back(d.back()*invs(n)), ++n;
    return d[i];
  }
  mint operator[](int i) const { return d[i];}
} ifacts;
mint comb(int n, int k) {
  if (n < k || k < 0) return 0;
  return facts(n)*ifacts(k)*ifacts(n-k);
}

int main() {
    int n, m, k;
    cin >> n >> m >> k;
    
    mint ans;
    rep(i, n) ans += mint(i)*(n-i)*m*m;
    rep(j, m) ans += mint(j)*(m-j)*n*n;
    ans *= comb(n*m-2, k-2);
    cout << ans.val() << '\n';
    
    return 0;
}

ABC130F. Enclosed Points

本题的核心在于转化计数对象!
不要直接计算集合 \(T\),而是转为统计“点 \(P\) 被计数的次数”的总和!

这样一来,问题就拆解为:

  • "\(T\) 包含点 \(P\) 时,其他点任意选" 的 \(2^{N-1}\) 种情况
  • \(T\) 不包含点 \(P\) 时,只有 \(T\) 斜跨这个点对应的一三象限或二四象限时满足。可以先用扫描线求出点 \(P\) 的四个象限上包含的点数,然后通过容斥解决。
代码实现
#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 P = pair<int, int>;
using mint = modint998244353;

mint f(int a, int b, int c, int d) {
    mint res;
    vector<int> num = {a, b, c, d};
    vector<mint> o(4), ox(4);
    rep(i, 4) {
        ox[i] = mint(2).pow(num[i]);
        o[i] = ox[i]-1;
    }
    res += ox[0]*o[1]*o[2]*ox[3];
    res += o[0]*ox[1]*ox[2]*o[3];
    res -= o[0]*o[1]*o[2]*o[3];
    
    res += ox[0]*ox[1]*ox[2]*ox[3];
    return res;
}

int main() {
    int n;
    cin >> n;
    
    vector<P> ps(n);
    rep(i, n) cin >> ps[i].first >> ps[i].second;
    
    { // compress x
        map<int, int> mp;
        rep(i, n) mp[ps[i].first] = 0;
        int j = 0;
        for (auto&& x : mp) x.second = j++;
        rep(i, n) ps[i].first = mp[ps[i].first];
    }
    { // compress y
        map<int, int> mp;
        rep(i, n) mp[ps[i].second] = 0;
        int j = 0;
        for (auto&& x : mp) x.second = j++;
        rep(i, n) ps[i].second = mp[ps[i].second];
    }
    
    ranges::sort(ps);
    
    vector<int> a(n), b(n), c(n), d(n);
    rep(_, 2) {
        { // calc a, b
            fenwick_tree<int> t(n);
            rep(i, n) {
                a[i] = t.sum(0, ps[i].second);
                b[i] = i-a[i];
                t.add(ps[i].second, 1);
            }
        }
        ranges::reverse(ps);
        swap(a, c);
        swap(b, d);
        ranges::reverse(a);
        ranges::reverse(b);
        ranges::reverse(c);
        ranges::reverse(d);
    }
    
    mint ans;
    rep(i, n) {
        ans += f(a[i], b[i], c[i], d[i]);
    }
    
    cout << ans.val() << '\n';
    
    return 0;
}

ABC149F. Surrounded Nodes

枚举包含在内部的白点,直接求包含这个点的最小连通块是困难的
但我们可以求它不包含在内部的最小连通块的个数
假设点 \(v\) 是包含在内部的白点,考察以点 \(v\) 为根的所有子树
全部染色数\((2^{N-1})\) \(-\) 都染白色 \((1)\) \(-\) 只有一个子树有黑点\((\sum (2^{T_{c_i}}-1))\)

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

int main() {
    int n;
    cin >> n;
    
    vector<vector<int>> to(n);
    rep(i, n-1) {
        int a, b;
        cin >> a >> b;
        --a; --b;
        to[a].push_back(b);
        to[b].push_back(a);
    }
    
    mint ans;
    auto dfs = [&](auto& f, int v, int p=-1) -> int {
        int res = 1;
        vector<int> ts;
        for (int u : to[v]) {
            if (u == p) continue;
            int t = f(f, u, v);
            res += t;
            ts.push_back(t);
        }
        if (p != -1) {
            ts.push_back(n-res);
        }
        mint now = mint(2).pow(n-1)-1;
        for (int t : ts) {
            now -= mint(2).pow(t)-1;
        }
        ans += now;
        return res;
    };
    dfs(dfs, 0);
    ans /= mint(2).pow(n);
    
    cout << ans.val() << '\n';
    
    return 0;
}

ABC204F. Hanjo 2

状压dp+矩阵快速幂

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

template<typename T>
struct Matrix {
  int h, w;
  vector<vector<T>> d;
  Matrix() {}
  Matrix(int h, int w, T val=0): h(h), w(w), d(h, vector<T>(w,val)) {}
  Matrix& unit() {
    assert(h == w);
    rep(i,h) d[i][i] = 1;
    return *this;
  }
  const vector<T>& operator[](int i) const { return d[i];}
  vector<T>& operator[](int i) { return d[i];}
  Matrix operator*(const Matrix& a) const {
    assert(w == a.h);
    Matrix r(h, a.w);
    rep(i,h)rep(k,w)rep(j,a.w) {
      r[i][j] += d[i][k]*a[k][j];
    }
    return r;
  }
  Matrix pow(long long t) const {
    assert(h == w);
    if (!t) return Matrix(h,h).unit();
    if (t == 1) return *this;
    Matrix r = pow(t>>1);
    r = r*r;
    if (t&1) r = r*(*this);
    return r;
  }
};

int main() {
    int h; ll w;
    cin >> h >> w;
    
    int n = 1<<h;
    Matrix<mint> d(n, n);
    
    rep(s, n) {
        rep(x, n) { // x集合:没有被左边一列占用的行
            if (x&s) continue; 
            for (int y = x;; y = (y-1)&x) { // y集合:选哪些行横着放2*1
                int ns = 0;
                rep(i, h) {
                    if (x>>i&1) {
                        if (y>>i&1) { // 横着放
                            ns |= 1<<i; // 向右延伸一格
                        }
                        else { // 竖着放
                            if (i == h-1) { ns = -1; break; }
                            if (s>>i>>1&1) { ns = -1; break; } // i+1已被左边占用
                            if (x>>i>>1&1) { ns = -1; break; } // i+1没在x中,没法配对
                        }
                    }
                }
                if (ns != -1) {
                    d[s][ns] += 1;
                }
                if (!y) break;
            }
        }
    }
    
    Matrix<mint> x(n, 1);
    x[0][0] = 1;
    x = d.pow(w)*x;
    mint ans = x[0][0];
    cout << ans.val() << '\n';
    
    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;
}

ABC229G. Longest Y

双指针

先预处理出每个 Y 左边有多少个 .,对于一段区间将所有的 Y 交换到一起意味着让区间里每个 Y 处左边的 . 数相同,而 “让区间里每个 Y 处左边的 . 数相同”的最小操作次数是个经典问题,就是 LC462。然后预处理一下 . 数序列的前缀和就可以做到 \(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() {
    string s; ll k;
    cin >> s >> k;
    int n = s.size();
    
    vector<int> a;
    rep(i, n) if (s[i] == 'Y') a.push_back(i-a.size());
    n = a.size();
    vector<ll> d(n+1);
    rep(i, n) d[i+1] = d[i]+a[i];
    
    int ans = 0;
    int r = 0;
    rep(l, n) {
        while (r < n) {
            int nr = r+1;
            int c = (l+nr)/2;
            ll now = ll(c-l)*a[c] - (d[c]-d[l]);
            now += (d[nr]-d[c]) - ll(nr-c)*a[c];
            if (now > k) break;
            r = nr;
        }
        ans = max(ans, r-l);
    }
    
    cout << ans << '\n';
    
    return 0;
}

ABC230F. Predilection

原题可以理解成对原序列划分成若干段,接着分别对每段求和,就得到了一个新的序列 \(B\),问能生成多少种本质不同的序列 \(B\)

可以发现每次操作,原序列的前缀和序列 \(S\) 中都会少一个数(显然不会删掉 \(S_0\)\(S_n\)),那么本题就转化成了有多少个本质不同的序列 \((S_1, S_2, \cdots, S_{n-1})\) 的子序列,而这个问题是经典的dp题。

dp[i] 表示最后取的是数字 \(S_i\) 的本质不同的子序列个数

转移方程:

\( \displaystyle dp[i] = \sum_{j \leqslant i-1} [S_i \ \text{上一次出现的位置} \leqslant j] \ dp[j] \)

可以用 map 来维护 \(S_i\) 上一次出现的位置

这样一来时间复杂度就是 \(\mathcal{O}(n^2)\)

下面考虑进行优化:

可以考虑容斥

\( \displaystyle dp[i+1] = dp[i] + (dp[i] - dp[\operatorname{last}[S_i]]) \)

代码实现
#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;
    cin >> n;
    
    vector<ll> a(n);
    rep(i, n) cin >> a[i];
    vector<ll> s(n+1);
    rep(i, n) s[i+1] = s[i]+a[i];
    s.erase(s.begin());
    s.pop_back();
    n--;
    
    mint ans = 1;
    map<ll, mint> dp;
    rep(i, n) {
        mint tmp = ans;
        ans += ans-dp[s[i]];
        dp[s[i]] = tmp;
    }
    
    cout << ans.val() << '\n';
    
    return 0;
}

ABC230G. GCD Permutation

\(f(A)\) 表示从 \(A\) 中任选两个数不互素的二元组的个数
那么答案就是 \(\sum\limits_{k=2}^n f(P_{ki})\mu'(k)\),这里的 \(P_{ki}\) 指由 \(P\) 中所有下标为 \(k\) 的倍数的数构成的子序列,\(\mu(k) = -\mu(k)\)

\(f(A) = \sum\limits_{k=2}^n \mu'(k) \times \text{能被}\, k \, \text{整除的} \, A_i \, 的数中任选两个数(可以两个都选同一个数)的方案数\)

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

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

struct Sieve {
	int n;
	vector<int> f, primes;
	Sieve(int n=1): n(n), f(n+1) {
		f[0] = f[1] = -1;
		for (ll i = 2; i <= n; ++i) {
			if (f[i]) continue;
			primes.push_back(i);
			f[i] = i;
			for (ll j = i*i; j <= n; j += i) {
				if (!f[j]) f[j] = i;
			}
		}
	}
	vector<int> factorList(int x) {
		vector<int> res;
		while (x != 1) {
			res.push_back(f[x]);
			x /= f[x];
		}
		return res;
	}
	vector<P> factor(int x) {
		vector<int> fl = factorList(x);
		if (fl.size() == 0) return {};
		vector<P> res(1, P(fl[0], 0));
		for (int p : fl) {
			if (res.back().first == p) {
				res.back().second++;
			}
			else {
				res.emplace_back(p, 1);
			}
		}
		return res;
	}
	vector<int> divisors2(int x) {
	    auto ps = factor(x);
	    vector<int> res{1};
	    for (auto [p, _] : ps) {
	        for (int i = res.size()-1; i >= 0; --i) {
	            res.push_back(res[i]*p);
	        }
	    }
	    res.erase(res.begin());
	    return res;
	}
};

ll c2(ll n) { return n*(n+1)/2; }

int main() {
    int n;
    cin >> n;
    
    Sieve prime(n);
    
    vector<int> a(n+1);
    rep(i, n) cin >> a[i+1];
    
    vector<int> mu(n+1, -1);
    mu[1] = 0;
    for (int p : prime.primes) {
        for (int i = p; i <= n; i += p) mu[i] = -mu[i];
        for (ll i = (ll)p*p; i <= n; i += (ll)p*p) mu[i] = 0;
    }
    
    vector<vector<int>> ds(n+1);
    for (int i = 1; i <= n; ++i) {
        ds[i] = prime.divisors2(i);
    }
    
    ll ans = 0;
    for (int k = 2; k <= n; ++k) {
        if (mu[k] == 0) continue;
        ll now = 0;
        unordered_map<int, int> s;
        for (int i = k; i <= n; i += k) {
            for (int x : ds[a[i]]) s[x]++;
        }
        for (auto [j, cnt] : s) now += c2(cnt)*mu[j];
        ans += now*mu[k];
    }
    
    cout << ans << '\n';
    
    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;
}

ABC235G. Gardens

先考虑条件1,就是个简单的容斥,见 盒子与球

再结合条件2,假设恰有 \(M\) 个花园对条件1没有限制,方案数就是

\[f(M) = \sum_{i=0}^a {}_MC_i \times \sum_{i=0}^b {}_MC_i \times \sum_{i=0}^c {}_MC_i \]

那么最终答案就是

\[\sum_{j=0}^N (-1)^{N-j} {}_NC_j f(j) \]

这样时间复杂度是 \(O(N^2)\)

考虑对组合数的前缀和进行加速

\(f_a(M) = \sum\limits_{i=0}^a {}_MC_i\)

有递推公式:\(f_a(M+1) = 2f_a(M) - {}_MC_{a}\)

那么我们就可以 \(O(N)\) 预处理出 \(f_a(0) \sim f_a(N)\)

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

struct modinv {
  int n; vector<mint> d;
  modinv(): n(2), d({0,1}) {}
  mint operator()(int i) {
    while (n <= i) d.push_back(-d[mint::mod()%n]*(mint::mod()/n)), ++n;
    return d[i];
  }
  mint operator[](int i) const { return d[i];}
} invs;
struct modfact {
  int n; vector<mint> d;
  modfact(): n(2), d({1,1}) {}
  mint operator()(int i) {
    while (n <= i) d.push_back(d.back()*n), ++n;
    return d[i];
  }
  mint operator[](int i) const { return d[i];}
} facts;
struct modfactinv {
  int n; vector<mint> d;
  modfactinv(): n(2), d({1,1}) {}
  mint operator()(int i) {
    while (n <= i) d.push_back(d.back()*invs(n)), ++n;
    return d[i];
  }
  mint operator[](int i) const { return d[i];}
} ifacts;
mint comb(int n, int k) {
  if (n < k || k < 0) return 0;
  return facts(n)*ifacts(k)*ifacts(n-k);
}

int main() {
    int n;
    cin >> n;
    
    vector<int> a(3);
    rep(i, 3) cin >> a[i];
    
    vector f(3, vector<mint>(n+1));
    rep(i, 3) {
        f[i][0] = 1;
        rep(j, n) {
            f[i][j+1] = f[i][j]*2;
            if (j >= a[i]) f[i][j+1] -= comb(j, a[i]);
        }
    }
    
    mint ans;
    rep(j, n+1) {
        mint now = 1;
        rep(i, 3) now *= f[i][j];
        now *= comb(n, j);
        ans = now-ans;
    }
    
    cout << ans.val() << '\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;
}

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;
}