T1. 逆序对数

对于任何子序列 \(S\),自然有 \(inv(S) \leqslant inv(p)\),取等当且仅当对于任何 \(i < j, a_i > a_j, i, j\) 都在 \(S\) 中。那么如果存在 \(j < i\) 使得 \(a_j > a_i\) 或是存在 \(j > i\) 使得 \(a_j < a_i\),那么 \(i\) 就必须在 \(S\) 中,预处理前后缀最值即可。设不必须在 \(S\) 中的元素有 \(k\) 个,那么答案就是 \(2^k\),特别地,当 \(k = n\) 时答案是 \(2^n-1\),因为要排除空子序列。
时间复杂度为 \(O(n)\)

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

void solve() {
    int n;
    cin >> n;
    
    vector<int> p(n);
    rep(i, n) cin >> p[i];
    
    vector<int> prefmax(n), sufmin(n);
    int mx = 0, mn = n+1;
    rep(i, n) {
        mx = max(mx, p[i]);
        if (p[i] == mx) prefmax[i] = 1;
        mn = min(mn, p[n-1-i]);
        if (p[n-1-i] == mn) sufmin[n-1-i] = 1;
    }
    
    int cnt = 0;
    rep(i, n) cnt += prefmax[i]*sufmin[i];
    
    mint ans = mint(2).pow(cnt);
    if (cnt == n) ans -= 1;
    cout << ans << '\n';
}

int main() {
    int t;
    cin >> t;
    
    while (t--) solve();
    
    return 0;
}

T2. 平衡 01 串

二分答案转为判定答案能否 \(\leqslant mid\),对于每个 \(l\),应取最大的 \(r\) 使得区间内的 \(0\) 的个数尽可能多并且不超过 \(mid\),然后当区间外的 \(1\) 的个数也不超过 \(mid\) 时即可行,于是可以做到 \(O(|S|\log |S|)\)

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

using namespace std;

void solve() {
    string s;
    cin >> s;
    int n = s.size();
    
    vector<int> sum(n+1);
    rep(i, n) sum[i+1] = sum[i] + (s[i] == '0');
    
    vector<int> is;
    rep(i, n) if (s[i] == '1') is.push_back(i);
    
    int m = is.size();
    int ac = m, wa = -1;
    while (abs(ac-wa) > 1) {
        int wj = (ac+wa)/2;
        
        bool ok = false;
        rep(i, wj+1) {
            int l = is[i], r = is[m-1-wj+i]+1;
            if (sum[r] - sum[l] <= wj) {
                ok = true;
                break;
            }
        }
        
        (ok ? ac : wa) = wj;
    }
    
    cout << ac << '\n';
}

int main() {
    int t;
    cin >> t;
    
    while (t--) solve();
    
    return 0;
}

T3. 城市漫步

\(x\) 为根,如果 \(x=y\),那么相当于最终每个关键点到根的路径上的边都会被经过两次(往返),按任意 \(\operatorname{dfs}\) 序遍历即可达到此下界,这也说明访问关键点的顺序可以任意,并且不影响达到下界。接下来考虑 \(y \neq x\) 时,到达最后一个关键点时要从返回 \(x\) 变成走向 \(y\),那么把 \(y\) 也视作关键点统计下界,最后走向 \(y\) 可不用返回,再减去 \(x\)\(y\) 的距离即可。
时间复杂度为 \(O(n)\)

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

using namespace std;

void solve() {
    int n, k, x, y;
    cin >> n >> k >> x >> y;
    --x; --y;
    
    vector<int> a(k);
    rep(i, k) cin >> a[i], a[i]--;
    a.push_back(y);
    
    vector<vector<int>> to(n);
    rep(i, n-1) {
        int u, v;
        cin >> u >> v;
        --u; --v;
        to[u].push_back(v);
        to[v].push_back(u);
    }
    
    vector<int> p(n, -1), dep(n);
    auto dfs = [&](auto& f, int v) -> void {
        for (int u : to[v]) {
            if (u == p[v]) continue;
            p[u] = v;
            dep[u] = dep[v]+1;
            f(f, u);
        }
    };
    dfs(dfs, x);
    
    int ans = 0;
    vector<bool> used(n);
    used[x] = true;
    for (int v : a) {
        while (!used[v]) {
            used[v] = true;
            ans += 2;
            v = p[v];
        }
    }
    ans -= dep[y];
    
    cout << ans << '\n';
}

int main() {
    int t;
    cin >> t;
    
    while (t--) solve();
    
    return 0;
}

T4. 随机游走

\(P(i, j)\) 表示经过 \(j-i\) 次操作后 \(l=i\)\(r=j\) 的概率,显然 \(P(i, i) = \frac{1}{n}\)\(P(1, n) = 1\),接下来只考虑长度至少为 \(2\) 且小于 \(n\) 的区间。

\(i > 1\)\(j < n\) 时显然有 \(P(i, j) = \frac{1}{2}P(i, j-1) + \frac{1}{2}P(i+1, j)\),简单归纳可得 \(P(i, j) = \frac{1}{n}(1 < i \leqslant j < n)\)

对于 \(i = 1\)\(j < n\) 的情况,有 \(P(1, j) = P(1, j-1) + \frac{1}{2}P(2, j) = P(1, j-1) + \frac{1}{2n}\),简单归纳可得 \(P(1, j) = \frac{j+1}{2n}(j < n)\)

同理有 \(P(i, n) = P(i+1, n) + \frac{1}{2}P(i, n-1) = P(i+1, n) + \frac{1}{2n}\),简单归纳可得 \(P(i, n) = \frac{n-i+2}{2n}(i > 1)\)

如果某个位置 \(i\) 是在区间长度为 \(j\) 时被扩展到的,那么 \(sum\) 会加上 \(j \times a_i\),那么其实可以看做是这 \(j\) 次扩展时每次都加了 \(a_i\)。由于每个位置最终都会被扩展到,因此 \(sum\) 必定会加上 \(a_i\),而剩下的 \((j-1) \times a_i\) 便是前 \(j-1\) 次扩展的贡献,而前 \(j-1\) 次扩展的共同点是它们都不包含 \(i\) 这个位置,也就是说 \(i\) 这个位置的贡献可以表示为

\( \begin{aligned} &\left(1+\sum_{i \notin [l, r]} P(l, r)\right) \times a_i\\ =& \left(1 + \sum_{l \leqslant r < i} P(l, r) + \sum_{i < l \leqslant r} P(l, r)\right) \times a_i\\ =& \left(1 + \sum_{r < i} P(1, r) + \sum_{1 < l \leqslant r < i} P(l, r) + \sum_{i < l} P(l, n) + \sum_{i < l \leqslant r < n} P(l, r)\right) \times a_i\\ =& \left(1 + \frac{\frac{i(i+1)}{2}-1}{2n} + \frac{\frac{(i-1)(i-2)}{2}}{n} + \frac{\frac{(n-i+1)(n-i+2)}{2}-1}{2n} + \frac{\frac{(n-i)(n-i-1)}{2}}{n}\right) \times a_i \end{aligned} \)

于是可以在 \(O(n)\) 时间内得到答案。

代码实现
#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 = 666528221;
// 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;
}

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

int main() {
    int n;
    cin >> n;
    
    vector<int> a(n);
    rep(i, n) cin >> a[i];
    
    mint ans;
    rep(i, n) {
        mint coef = 1;
        coef += mint(c2(i+2)-1)/(2*n);
        coef += mint(c2(i))/n;
        coef += mint(c2(n-i+1)-1)/(2*n);
        coef += mint(c2(n-i-1))/n;
        ans += coef*a[i];
    }
    
    cout << ans << '\n';
    
    return 0;
}