C. Sum of Product

\( \displaystyle \sum_{1 \leqslant i < j \leqslant N} A_iA_j = \frac{\sum\limits_{1 \leqslant i \leqslant j \leqslant N} A_iA_j - \sum\limits_{1 \leqslant i \leqslant N} A_i^2}{2} = \frac{(\sum A_i)^2 - \sum A_i^2}{2} \)

代码实现
n = int(input())
a = list(map(int, input().split()))

s, s2 = 0, 0
for i in range(n):
    s += a[i]
for i in range(n):
    s2 += a[i]*a[i]
ans = (s*s-s2)//2
print(ans)

D. Escape Route

以每个紧急出口为起点跑多源bfs,注意答案的方向要反过来

代码实现
#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};
const string dc = "^<v>";

int main() {
    int h, w;
    cin >> h >> w;
    
    vector<string> s(h);
    rep(i, h) cin >> s[i];
    
    queue<P> q;
    rep(i, h)rep(j, w) {
        if (s[i][j] == 'E') {
            q.emplace(i, j);
        }
    }
    
    while (q.size()) {
        auto [i, j] = q.front(); q.pop();
        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 (s[ni][nj] != '.') continue;
            q.emplace(ni, nj);
            s[ni][nj] = dc[v^2];
        }
    }
    
    rep(i, h) cout << s[i] << '\n';

    return 0;
}

E. Fruit Lineup

先将苹果和橙子都排列好,枚举有多少个橙子放在最后一个苹果的左边,葡萄只能放在它们的右侧,然后将香蕉插进右边的橙子和葡萄之间

代码实现
#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 a, b, c, d;
    cin >> a >> b >> c >> d;
    
    mint ans;
    rep(l, b+1) {
        mint now = comb(a-1+l, l);
        now *= comb((b-l+d)+c, c);
        ans += now;
    }
    
    cout << ans.val() << '\n';
    
    return 0;
}

F. Chord Crossing

可以先将圆切开,转化为“与多少个区间相交?”的问题,然后将区间绘制在平面上,变成“矩形区域内有多少个点?”的问题,通过离线+用线段树做扫描线即可解决!

还有另一种做法:
将区间抽象成点,一个区间 \(a\) 覆盖另一个区间 \(b\),那么可以连一条 \(a\) 指向 \(b\) 的边,最后可以得到一个森林,然后开一个超级源点 \(0\),将它连向所有树的根节点,这样就得到了一颗树。对于询问,其实就是求树上两点之间的最短路。
可以用栈来完成建图

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

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

template<typename T=int>
struct lca {
    int n, l;
    vector<vector<int>> to;
    vector<vector<T>> co;
    vector<int> dep;
    vector<T> costs;
    vector<vector<int>> par;
    lca(int n): n(n), to(n), co(n), dep(n), costs(n) {
        l = 0;
        while (1<<l <= n) ++l;
        par = vector<vector<int>>(n, vector<int>(l, -1));
    }
    void addEdge(int a, int b, T c=1) {
        to[a].push_back(b); co[a].push_back(c);
        to[b].push_back(a); co[b].push_back(c);
    }
    void dfs(int v, int d=0, T c=0, int p=-1) {
        par[v][0] = p;
        dep[v] = d;
        costs[v] = c;
        rep(i, to[v].size()) {
            int u = to[v][i];
            if (u == p) continue;
            dfs(u, d+1, c+co[v][i], v);
        }
    }
    void init(int root=0) {
        dfs(root);
        rep(i, l-1) {
            rep(v, n) {
                par[v][i+1] = par[v][i]==-1 ? -1 : par[par[v][i]][i];
            }
        }
    }
    // LCA
    int operator()(int a, int b) {
        if (dep[a] > dep[b]) swap(a, b);
        int gap = dep[b]-dep[a];
        for (int i = l-1; i >= 0; --i) {
            int len = 1<<i;
            if (gap >= len) {
                gap -= len;
                b = par[b][i];
            }
        }
        if (a == b) return a;
        for (int i = l-1; i >= 0; --i) {
            int na = par[a][i];
            int nb = par[b][i];
            if (na != nb) a = na, b = nb;
        }
        return par[a][0];
    }
    int length(int a, int b) {
        int c = (*this)(a, b);
        return dep[a]+dep[b]-dep[c]*2;
    }
    T dist(int a, int b) {
        int c = (*this)(a, b);
        return costs[a]+costs[b]-costs[c]*2;
    }
};

int main() {
    int n, m;
    cin >> n >> m;
    n *= 2;
    
    vector<int> dif(n);
    rep(i, m) {
        int a, b;
        cin >> a >> b;
        --a; --b;
        dif[a] = 1; dif[b] = -1;
    }
    
    vector<P> es;
    vector<int> vid(n);
    {
        vector<int> st = {0};
        int vs = 1;
        rep(i, n) {
            if (dif[i] == 0) {
                vid[i] = st.back();
            }
            else {
                if (dif[i] == 1) {
                    es.emplace_back(st.back(), vs);
                    st.push_back(vs); vs++;
                }
                else {
                    st.pop_back();
                }
            }
        }
    }
    
    lca g(n);
    for (auto [a, b] : es) g.addEdge(a, b);
    g.init();
    
    int q;
    cin >> q; 
    rep(qi, q) {
        int c, d;
        cin >> c >> d;
        --c; --d;
        cout << g.length(vid[c], vid[d]) << '\n';
    }
    
    return 0;
}

G. Range Shuffle Query

莫队
如果没有 \(X\) 这个限制,答案就是 \(\frac{(R-L+1)!}{(1 \ \text{的个数}!)(2 \ \text{的个数}!) \cdots}\),所以只需知道每个数的阶乘,用莫队就能解决。如果有 \(X\) 这个限制,只需求出部分阶乘的乘积,所以用树状数组来维护 \(i \ \text{的个数}!\) 即可。
然后发现会TLE。
注意到,这个问题利用了“当单点更新的次数远多于区间和查询时,分块比线段树更快”这一点,所以只需在原做法的基础上,把线段树换成分块就能过了。

代码实现
#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 Q = tuple<int, int, int>;
using mint = modint998244353;

vector<mint> invs;
vector<mint> facs;

struct X {
    int sum; mint prod;
    X(): sum(0), prod(1) {}
    void operator+=(const X& x) {
        sum += x.sum;
        prod *= x.prod;
    }
};
struct SqDec {
    int D;
    vector<X> a, s;
    SqDec(int n) {
        D = max<int>(1, sqrt(n));
        a.resize(n);
        s.resize(n/D+1);
    }
    void add(int i, int sign) {
        X x; 
        x.sum = sign;
        if (sign == 1) x.prod = invs[a[i].sum+1];
        else x.prod = a[i].sum;
        a[i] += x;
        s[i/D] += x;
    }
    X sum(int r) {
        X res;
        for (int i = 0, si = 0; i < r;) {
            if (i+D <= r) {
                res += s[si++];
                i += D;
            } 
            else {
                res += a[i++];
            }
        }
        return res;
    }
};

int main() {
    int n, q;
    cin >> n >> q;
    
    vector<int> a(n);
    rep(i, n) cin >> a[i], a[i]--;
    
    invs.resize(n+1);
    for (int i = 1; i <= n; ++i) invs[i] = mint(i).inv(); 
    facs.resize(n+1, 1);
    for (int i = 1; i <= n; ++i) facs[i] = facs[i-1]*i; 
    
    vector<Q> qs;
    rep(i, q) {
        int l, r, x;
        cin >> l >> r >> x;
        --l; --x;
        qs.emplace_back(l, r, x);
    }
    
    vector<int> qis(q);
    rep(i, q) qis[i] = i;
    int D = max<int>(1, n/sqrt(q));
    sort(qis.begin(), qis.end(), [&](int i, int j) {
        auto [li, ri, xi] = qs[i];
        auto [lj, rj, xj] = qs[j];
        return P(li/D, ri) < P(lj/D, rj);
    });
    
    vector<int> ans(q);
    int l = 0, r = 0;
    SqDec sd(n);
    for (int qi : qis) {
        auto [nl, nr, nx] = qs[qi];
        while (nl < l) l--, sd.add(a[l], 1);
        while (r < nr) sd.add(a[r], 1), r++;
        while (l < nl) sd.add(a[l], -1), l++;
        while (nr < r) r--, sd.add(a[r], -1);
        X x = sd.sum(nx);
        ans[qi] = (facs[x.sum]*x.prod).val();
    }
    
    rep(i, q) cout << ans[i] << '\n';
    
    return 0;
}