C. Sum of Min Query
差分更新
原本需要计算 \(N\) 个数的和,但每次查询时只会改变 \(\min(A_{X_i}, B_{X_i})\) 这一个值,所以只要检查这个值的变化情况,就能以 \(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() {
int n, q;
cin >> n >> q;
vector<int> a(n), b(n);
rep(i, n) cin >> a[i];
rep(i, n) cin >> b[i];
ll ans = 0;
rep(i, n) ans += min(a[i], b[i]);
rep(qi, q) {
char c; int x, v;
cin >> c >> x >> v;
--x;
ans -= min(a[x], b[x]);
if (c == 'A') a[x] = v; else b[x] = v;
ans += min(a[x], b[x]);
cout << ans << '\n';
}
return 0;
}
D. Toggle Maze
拆点+BFS(分层图)
我们需要考虑两种状态世界——开关被按偶数次的世界和开关被按奇数次的世界,并在它们之间来回切换。
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
const int di[] = {-1, 0, 1, 0};
const int dj[] = {0, 1, 0, -1};
int dist[505][505][2];
int main() {
int h, w;
cin >> h >> w;
vector<string> a(h);
rep(i, h) cin >> a[i];
const int INF = 1001001001;
rep(i, h)rep(j, w)rep(k, 2) dist[i][j][k] = INF;
queue<tuple<int, int, int>> q;
auto push = [&](int i, int j, int k, int d) {
if (dist[i][j][k] != INF) return;
dist[i][j][k] = d;
q.emplace(i, j, k);
};
rep(i, h)rep(j, w) if (a[i][j] == 'S') push(i, j, 0, 0);
while (q.size()) {
auto [i, j, k] = q.front(); q.pop();
int d = dist[i][j][k];
if (a[i][j] == 'G') {
cout << d << '\n';
return 0;
}
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 (a[ni][nj] == '#') continue;
if (k == 0 and a[ni][nj] == 'x') continue;
if (k == 1 and a[ni][nj] == 'o') continue;
int nk = k;
if (a[ni][nj] == '?') nk ^= 1;
push(ni, nj, nk, d+1);
}
}
puts("-1");
return 0;
}
E. Reachability Query
并查集
只需额外维护每个连通分量中黑色顶点的数量即可!
代码实现
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
int main() {
int n, q;
cin >> n >> q;
dsu uf(n);
vector<int> col(n), cnt(n);
rep(qi, q) {
int type, v;
cin >> type >> v;
--v;
if (type == 1) {
int u;
cin >> u;
--u;
u = uf.leader(u); v = uf.leader(v);
if (u == v) continue;
int black = cnt[u]+cnt[v];
uf.merge(u, v);
cnt[uf.leader(u)] = black;
}
else if (type == 2) {
cnt[uf.leader(v)] -= col[v];
col[v] ^= 1;
cnt[uf.leader(v)] += col[v];
}
else {
v = uf.leader(v);
if (cnt[v]) puts("Yes");
else puts("No");
}
}
return 0;
}
F. kirinuki
按「行」枚举矩形的 底边(把当前遍历的行看作矩形的底行)。对于固定底行 bi:
- 先计算每列的高度
a[j],即以当前行为底向上连续'.'的最大高度(遇到#就置 0)。 - 那么,任一以当前行为底、竖向高度为
t(1 ≤ t ≤ a[j])的、横跨某一列区间[ly,ry]的矩形是全'.'的,当且仅当该区间的 最小高度 ≥ t。 - 因此问题可等价为:对当前高度数组
a[0..m-1],枚举所有区间[L,R],并对能选用的高度t = 1..min_{j∈[L,R]} a[j]统计满足t * width ≤ K的高度数(这里width = R-L+1)。把这些对每一个底行累加起来就是最终答案。
关键难点是:要高效地对同一个底行、对所有区间进行计数(不能枚举所有 \(O(M^2)\) 区间)。使用笛卡尔树把所有区间按“区间最小值所在位置”进行一次性唯一分配,从而把复杂度降下来。
如何在单个节点 v 上统计贡献
固定节点 v,设其高度 h = a[v],在它负责的左右范围内共有 A = left span = v - L 种向左扩展长度(取 1..A)和 B = right span = R - v 向右扩展长度(取 1..B)。任意选 a(左扩展)和 b(右扩展),宽 w = a + b - 1,该区间的最小值就是在 v,且允许的高度 t 可从 1 到 h。但我们还要满足面积约束 t * w ≤ K。
对于固定高度 t,允许的最大宽是 W(t) = floor(K / t)。于是固定 t,被 v 负责并且满足面积约束的区间数等于:
count_t = # of pairs (a,b) with 1≤a≤A, 1≤b≤B, a+b-1 ≤ W(t)
把 p=a-1, q=b-1 变换为非负整数,就变成:
#(p,q ≥ 0, p ≤ A-1, q ≤ B-1, p+q ≤ W(t)-1).
无上界时 #(p+q ≤ S) 的值等于 tri(S) = (S+2)(S+1)/2(若 S>=0,否则 0)。按有限上界用常规的容斥,可以得到:
count_t = tri(F)
- tri(F - A)
- tri(F - B)
+ tri(F - A - B)
(画个图就能看出来了)
其中 F = W(t)-1 = floor(K/t) -1,当 tri 的参数为负时定义为 0。
于是节点 v 的总贡献为对 t = 1..h 把上式相加。把 t 的求和与 tri(...) 的 F(=floor(K/t)-1)结合后,先把
f[h][c] = sum_{t=1..h} tri( floor(K/t) - 1 - c )
预计算好(c 是容斥时需要移动的常量)。然后节点 v 的贡献用一次常数时间的组合:
contrib(v) = sum_{t=1..h} [ tri(F) - tri(F-A) - tri(F-B) + tri(F-A-B) ]
= f[h][0] - f[h][A] - f[h][B] + f[h][A+B]
这样就把原本对每个 t 的重复循环通过预处理 f 合并成了 \(O(1)\) 的操作。
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
template<class T=long long>
struct CartesianTree {
int n, root;
vector<int> l, r;
CartesianTree() {}
CartesianTree(const vector<T>& a, bool _max=true) {
n = a.size();
l = r = vector<int>(n, -1);
vector<int> st;
rep(i, n) {
int p = -1;
while (st.size() and !((a[st.back()] < a[i]) ^ _max)) {
int j = st.back(); st.pop_back();
r[j] = p; p = j;
}
l[i] = p;
st.push_back(i);
}
rep(i, st.size()-1) r[st[i]] = st[i+1];
root = st[0];
}
};
int main() {
int n, m, k;
cin >> n >> m >> k;
vector<string> s(n);
rep(i, n) cin >> s[i];
ll ans = 0;
vector f(n+1, vector<ll>(m+2));
auto tri = [&](ll s) -> ll {
if (s < 0) return 0;
return (s+2)*(s+1)/2;
};
for (int h = 1; h <= n; ++h)rep(c, m+2) {
f[h][c] = f[h-1][c] + tri(k/h-1-c);
}
auto add = [&](int l, int r, int h) {
l++; r++;
ans += f[h][0];
ans -= f[h][l];
ans -= f[h][r];
ans += f[h][l+r];
};
vector<int> a(m);
rep(bi, n) {
rep(i, m) {
a[i]++;
if (s[bi][i] == '#') a[i] = 0;
}
CartesianTree t(a, false);
auto dfs = [&](auto& f, int l, int r, int v) -> void {
if (v == -1) return;
add(v-l, r-v, a[v]);
f(f, l, v-1, t.l[v]);
f(f, v+1, r, t.r[v]);
};
dfs(dfs, 0, m-1, t.root);
}
cout << ans << '\n';
return 0;
}
G. sqrt(n²+n+X)
假设 \(n^2+n+X=m^2\)
\((n+\frac{1}{2})^2 - \frac{1}{4} + X = m^2\)
\((2n+1)^2 - 1 + 4X = 4m^2\)
\((2m)^2 - (2n+1)^2 = 4X-1\)
\(((2m)+(2n+1))((2m)-(2n+1)) = 4X-1\)
假设 \(a=(2m)+(2n+1)\),\(b=(2m)-(2n+1)\)
\((2n+1)+(2n+1) = a-b\)
\(4n=a-b-2\)
\(n=\frac{a-b-2}{4}\)
而 \(a,b\) 都是 \(4X-1\) 的因子,那么我们只需枚举 \(4X-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() {
ll x;
cin >> x;
x = 4*x-1;
set<ll> ans;
for (ll i = 1; i*i <= abs(x); ++i) {
if (x%i) continue;
ll a = i, b = x/i;
rep(sgn, 2) {
rep(swp, 2) {
if ((a-b-2)%4 == 0) {
ll n = (a-b-2)/4;
ans.insert(n);
}
swap(a, b);
}
a = -a; b = -b;
}
}
cout << ans.size() << '\n';
for (ll x : ans) cout << x << ' ';
return 0;
}
浙公网安备 33010602011771号