C. 1D puyopuyo
栈
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
int main() {
int n;
cin >> n;
vector<int> s;
rep(i, n) {
int a;
cin >> a;
s.push_back(a);
if (s.size() >= 4) {
bool ok = true;
rep(j, 3) {
if (s[s.size()-2-j] != s.back()) ok = false;
}
if (ok) {
rep(j, 4) s.pop_back();
}
}
}
cout << s.size() << '\n';
return 0;
}
D. Tail of Snake
两种做法
一种做法是,倒着枚举第一个分割位置 \(x\),它左边就全都是选 \(A_i\),右边是先有一段 \(B_i\) 后面全都是 \(C_i\)
右边的贡献可以转化成右边的 \(\sum B_i\) 加上 \(\{\sum (C_i-B_i)\}\) 的后缀最大值
代码实现
#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;
cin >> n;
vector<int> a(n), b(n), c(n);
rep(i, n) cin >> a[i];
rep(i, n) cin >> b[i];
rep(i, n) cin >> c[i];
ll ans = 0;
ll sum_a = 0;
rep(i, n) sum_a += a[i];
ll base = 0, diff = 0;
ll max_diff = -1e18;
for (int x = n-1; x > 0; --x) {
sum_a -= a[x];
base += b[x];
diff += c[x]-b[x];
ll max_bc = base + max_diff;
ans = max(ans, sum_a + max_bc);
max_diff = max(max_diff, diff);
}
cout << ans << '\n';
return 0;
}
另一种做法是考虑dp,这种做法最简单
记 dp[i][0/1/2] 表示到第 \(i\) 个位置时,且最后一个位置选了 \(A_i/B_i/C_i\)
实际意义其实就是求从点 \((0, 0)\) 通过向右或斜向下走到点 \((n-1, 2)\) 的最大路径和
代码实现
#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;
cin >> n;
vector a(n, vector<int>(3));
rep(i, 3)rep(j, n) cin >> a[j][i];
const ll INF = 1e18;
vector dp(n, vector<ll>(3, -INF));
dp[0][0] = a[0][0];
for (int i = 1; i < n; ++i) {
rep(j, 3) {
ll mx = dp[i-1][j];
if (j) mx = max(mx, dp[i-1][j-1]);
dp[i][j] = mx + a[i][j];
}
}
cout << dp[n-1][2] << '\n';
return 0;
}
E. Heavy Buckets
倍增
代码实现
#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);
rep(i, n) cin >> a[i], a[i]--;
const int D = 30;
vector to(D, vector<int>(n));
vector sum(D, vector<ll>(n));
rep(i, n) to[0][i] = a[i];
rep(i, n) sum[0][i] = i+1;
rep(j, D-1)rep(i, n) {
int x = to[j][i];
to[j+1][i] = to[j][x];
sum[j+1][i] = sum[j][i] + sum[j][x];
}
rep(qi, q) {
int t, b;
cin >> t >> b;
--b;
ll ans = 0;
rep(j, D) if (t>>j&1) {
ans += sum[j][b];
b = to[j][b];
}
cout << ans << '\n';
}
return 0;
}
F. Sum of Mex
先考虑这题的序列版本:
给定 \((0, 1, \cdots, N-1)\) 的某个排列 \(A = (A_1, A_2, \cdots, A_N)\).
求 \(\displaystyle \sum_{l=1}^N\sum_{r=l}^N mex(A_l, A_{l+1}, \cdots, A_r)\) .
限制:
- \(1 \leqslant N \leqslant 2 \times 10^5\)
注意到:“\(\operatorname{mex}\) 是区间中未出现的最小非负整数”等价于“整数 \(0, 1, 2, \cdots, \operatorname{mex}-1\) 均已经出现在区间中”。
那么,我们可以考虑每个数对于所有大于它的数作为 \(mex\) 的贡献,具体地,可以枚举 \(k = 0, 1, 2, \cdots, N-1\),统计有多少个区间 \([l, r]\) 满足 \(\text{mex}(A_l, A_{l+1}, \cdots, A_r) > k\) .
上树的话本质上也是类似的,可以转成求 \(\text{mex}(\text{path}(l, r)) > k\) 的路径数
当遍历到 \(k\) 时,就不断跳父节点,直到遇到已经遍历过的点时停止。
如果从 \(k\) 能跳到 \(l\),那么就令 \(l \gets k\);如果从 \(k\) 能跳到 \(r\),那么就令 \(r \gets k\);否则,就说明跳到了路径 \(\text{path}(l, r)\) 之间不在端点上的点,这种情况对路径 \(\text{path}(l, r)\) 的扩张无影响
那么 \(k\) 对应的贡献就是 \(l\) 的子树大小 \(\times\) \(r\) 的子树大小
还需注意,当跳到 \(0\) 时需要把点 \(0\) 的子树大小减去跳到 \(0\) 之前的点的子树大小
代码实现
#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;
cin >> n;
vector<vector<int>> to(n);
rep(i, n-1) {
int a, b;
cin >> a >> b;
to[a].push_back(b);
to[b].push_back(a);
}
vector<int> pa(n);
vector<int> t(n);
auto dfs = [&](auto& f, int v, int p=-1) -> int {
pa[v] = p;
t[v] = 1;
for (int u : to[v]) {
if (u == p) continue;
t[v] += f(f, u, v);
}
return t[v];
};
dfs(dfs, 0);
ll ans = 0;
{
auto f = [&](ll x) { return x*(x+1)/2; };
ans = f(n);
for (int v : to[0]) ans -= f(t[v]);
}
vector<bool> used(n);
used[0] = true;
int l = 0, r = 0;
for (int i = 1; i < n; ++i) {
if (!used[i]) {
int v = i, pre = -1;
while (!used[v]) {
used[v] = true;
pre = v; v = pa[v];
}
if (v == 0) t[0] -= t[pre];
if (l == v) l = i;
else if (r == v) r = i;
else break;
}
ans += (ll)t[l]*t[r];
}
cout << ans << '\n';
return 0;
}
G. Sum of Min
\(\min(a,b) = \sum\limits_{k≥1} [a≥k \wedge b≥k]\)。
考虑贡献。通过把 \(k\) 适当合并,只需考虑不超过 \(N+M\) 个不同的 \(k\) 值。考虑 \(k\) 递增的过程,\(a≥k\) 或 \(b≥k\) 在某个时刻会变为 \(\text{false}\);我们只需要知道在变为 \(\text{false}\) 的那一刻,仍然为 \(\text{true}\) 的“另一方”的个数。通过适当重排索引,可以使下一“轮”中的对应元素恰好是上一次对应元素的下一个,这样为 \(\text{true}\) 的个数就可以作为区间和来获得。
浙公网安备 33010602011771号