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