2024初秋集训——提高组 #28
B. 车轮战
题目描述
你将进行 \(N\) 场决斗。一开始你的战斗力为 \(s\),咒术强度为 \(x\)。每次决斗之前你可以选择:
- 令 \(s\leftarrow s+x\)。
- 令 \(x\leftarrow x+1\)。
每次决斗,如果你的 \(s\ge f_i\),则你赢得决斗。求最多能赢多少场决斗。
思路
我们可以发现,你最多会进行 \(80\) 次操作 \(2\),因为如果你当前已经做了 \(80\) 次,那么接下来再做一次操作 \(2\),则会损失至少 \(80\) 的战斗力,那么你又要用 \(80\) 次操作才能弥补回来,而此时战斗力已经至少有 \(80^2=6400>5000\),所以不做这次操作也能达到这么多,已经能够赢得所有的决斗了。
由此,我们就知道至多会输 \(160\) 场,就是先做 \(80\) 次操作 \(2\),再做 \(80\) 次操作 \(1\)。
我们令 \(dp_{i,j,k}\) 表示考虑前 \(i\) 场,当前 \(x=j\),输了 \(k\) 场的最大 \(s\)。
时空复杂度均为 \(O(N\max\{f_i\})\)。
代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5001;
int t, n, s, x, a[MAXN], ans, dp[MAXN][81][161];
void Solve() {
cin >> n >> s >> x;
ans = 0;
for(int i = 1; i <= n; ++i) {
cin >> a[i];
}
for(int i = 0; i <= n; ++i) {
for(int j = 0; j <= 75; ++j) {
for(int k = 0; k <= 140; ++k) {
dp[i][j][k] = -int(1e9);
}
}
}
dp[0][0][0] = s;
for(int i = 0; i < n; ++i) {
for(int j = 0; j <= 75; ++j) {
for(int k = 0; k <= 140; ++k) {
if(dp[i][j][k] < s) {
continue;
}
if(j < 75 && k + (dp[i][j][k] < a[i + 1]) <= 160) {
dp[i + 1][j + 1][k + (dp[i][j][k] < a[i + 1])] = max(dp[i + 1][j + 1][k + (dp[i][j][k] < a[i + 1])], dp[i][j][k]);
}
if(k + (dp[i][j][k] + j + x < a[i + 1]) <= 160) {
dp[i + 1][j][k + (dp[i][j][k] + j + x < a[i + 1])] = max(dp[i + 1][j][k + (dp[i][j][k] + j + x < a[i + 1])], dp[i][j][k] + j + x);
}
}
}
}
for(int i = 0; i <= 160; ++i) {
for(int j = 0; j <= 75; ++j) {
if(dp[n][j][i] >= s) {
cout << n - i << "\n";
return;
}
}
}
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
for(cin >> t; t--; Solve()) {
}
return 0;
}
C. 清理地板
题目描述
有 \(K\) 个垃圾在 \(N\times M\) 的网格图上,其不干净程度为 \(val_i\),你要选择三个行/列,并把这些行/列打扫,每个垃圾被打扫多次只算一遍。
求打扫的垃圾不干净程度总和最大值。
思路
首先我们处理好选三行/三列的情况。
我们先枚举其中一个行/列,并用 map
存下每一行/列上的垃圾。接着依次把这些垃圾对应的列/行减去其贡献。用 set
维护其最大值,次大值即可。
空间复杂度 \(O(K)\),时间复杂度 \(O(K\log K)\)。
代码
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<ll, ll>;
const int MAXN = 101;
int t, n, m, k;
map<int, ll> sum[2];
map<int, vector<pii>> ve[2];
set<pii, greater<pii>> s[2];
ll ans;
void Solve() {
cin >> n >> m >> k;
ans = 0;
sum[0].clear(), sum[1].clear();
ve[0].clear(), ve[1].clear();
s[0].clear(), s[1].clear();
for(int i = 1, x, y, v; i <= k; ++i) {
cin >> x >> y >> v;
sum[0][x] += v;
sum[1][y] += v;
ve[0][x].emplace_back(y, v);
ve[1][y].emplace_back(x, v);
}
for(auto [key, val] : sum[0]) {
s[0].insert({val, key});
}
for(auto [key, val] : sum[1]) {
s[1].insert({val, key});
}
ans = max(s[0].begin()->first + (s[0].size() > 1 ? next(s[0].begin())->first : 0) + (s[0].size() > 2 ? next(next(s[0].begin()))->first : 0),
s[1].begin()->first + (s[1].size() > 1 ? next(s[1].begin())->first : 0) + (s[1].size() > 2 ? next(next(s[1].begin()))->first : 0));
for(auto [val, pos] : s[0]) {
for(auto [p, v] : ve[0][pos]) {
s[1].erase({sum[1][p], p});
sum[1][p] -= v;
s[1].insert({sum[1][p], p});
}
ans = max(ans, val + s[1].begin()->first + (s[1].size() > 1 ? next(s[1].begin())->first : 0));
for(auto [p, v] : ve[0][pos]) {
s[1].erase({sum[1][p], p});
sum[1][p] += v;
s[1].insert({sum[1][p], p});
}
}
for(auto [val, pos] : s[1]) {
for(auto [p, v] : ve[1][pos]) {
s[0].erase({sum[0][p], p});
sum[0][p] -= v;
s[0].insert({sum[0][p], p});
}
ans = max(ans, val + s[0].begin()->first + (s[0].size() > 1 ? next(s[0].begin())->first : 0));
for(auto [p, v] : ve[1][pos]) {
s[0].erase({sum[0][p], p});
sum[0][p] += v;
s[0].insert({sum[0][p], p});
}
}
cout << ans << "\n";
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
for(cin >> t; t--; Solve()) {
}
return 0;
}
D. 兵力调配
题目描述
有 \(N\) 个城市,由 \(N-1\) 条道路连通。每个城市里有 \(A_i\) 个士兵,你想让每个城市有 \(B_i\) 个士兵(\(\sum A_i=\sum B_i\))。你每把一个士兵移动过一个道路需要 \(1\) 的花费。但是有 \(M\) 条废弃道路,你可以选择一条重新启用。
求达到目标的最小花费。
思路
我们先考虑 \(M=0\) 的情况。一个子树对答案的贡献为 \(|\sum (A_i-B_i)|\)。
而加了边,则会变成一个基环树。环以外的部分按照原来的方法处理。而环上必定有一条边不会用到,否则只有这两种情况:
像这样绕回自己肯定没有意义。
而像这样兵分两路肯定其中一路更短,也不可能出现。
所以我们可以枚举环上的每条边,并用树状数组维护 \(\le 0,>0\) 的部分即可。
空间复杂度 \(O(N)\),时间复杂度 \(O(NM\log N)\)。
代码
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 400005;
struct Tree_Array {
int n;
ll tr[MAXN];
void Clear(int m) {
n = m;
fill(tr + 1, tr + n + 1, 0);
}
void update(int p, ll x) {
for(; p <= n; tr[p] += x, p += (p & -p)) {
}
}
ll Getsum(int p) {
ll sum = 0;
for(; p; sum += tr[p], p -= (p & -p)) {
}
return sum;
}
ll query(int l, int r) {
return Getsum(r) - Getsum(l - 1);
}
}tr, tr2;
int n, m, a[MAXN], stk[MAXN], top, tot, pos[MAXN], w[MAXN];
bool vis[MAXN];
map<int, bool> mp[MAXN];
vector<int> e[MAXN], ve;
ll sum[MAXN], ans, res, pre[MAXN];
void dfs(int u, int fa) {
sum[u] = a[u];
for(int v : e[u]) {
if(v != fa) {
dfs(v, u);
sum[u] += sum[v];
}
}
ans += abs(sum[u]);
}
bool DFS(int u, int fa) {
if(vis[u]) {
for(; stk[top] != u; ve.emplace_back(stk[top--])) {
}
ve.emplace_back(u);
return 1;
}
stk[++top] = u, vis[u] = 1;
for(int v : e[u]) {
if(v != fa && DFS(v, u)) {
return 1;
}
}
top--;
return 0;
}
void Dfs(int u, int fa) {
sum[u] = a[u];
for(int v : e[u]) {
if(v != fa && !vis[v]) {
Dfs(v, u);
sum[u] += sum[v];
}
}
if(!vis[u]) {
res += abs(sum[u]);
}
}
void add(int u, int v) {
if(mp[u].count(v)) {
return;
}
e[u].emplace_back(v);
e[v].emplace_back(u);
fill(vis + 1, vis + n + 1, 0);
ve.clear();
top = 0;
DFS(1, 0);
fill(vis + 1, vis + n + 1, 0);
tot = 0;
for(int x : ve) {
vis[x] = 1;
pos[tot + ve.size()] = pos[++tot] = x;
}
res = 0;
for(int x : ve) {
Dfs(x, 0);
}
vector<ll> vec;
pre[0] = 0;
for(int i = 1; i <= 2 * tot; ++i) {
pre[i] = pre[i - 1] + sum[pos[i]];
vec.emplace_back(pre[i]);
}
vec.emplace_back(0);
sort(vec.begin(), vec.end());
vec.erase(unique(vec.begin(), vec.end()), vec.end());
for(int i = 0; i <= 2 * tot; ++i) {
w[i] = lower_bound(vec.begin(), vec.end(), pre[i]) - vec.begin() + 1;
}
ll now = (ll)(1e18);
tr.Clear(2 * n + 1), tr2.Clear(2 * n + 1);
for(int i = 1; i < tot; ++i) {
tr.update(w[i], 1);
tr2.update(w[i], pre[i]);
}
for(int l = 1, r = tot - 1; l <= tot; ++l, ++r) {
now = min(now, 1ll * tr.query(1, w[l - 1]) * pre[l - 1] - tr2.query(1, w[l - 1]) + tr2.query(w[l - 1] + 1, 2 * n + 1) - 1ll * tr.query(w[l - 1] + 1, 2 * n + 1) * pre[l - 1]);
tr.update(w[l], -1);
tr2.update(w[l], -pre[l]);
tr.update(w[r + 1], 1);
tr2.update(w[r + 1], pre[r + 1]);
}
e[u].pop_back(), e[v].pop_back();
ans = min(ans, now + res);
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; ++i) {
cin >> a[i];
}
for(int i = 1, x; i <= n; ++i) {
cin >> x;
a[i] -= x;
}
for(int i = 1, u, v; i < n; ++i) {
cin >> u >> v;
e[u].emplace_back(v);
e[v].emplace_back(u);
mp[u][v] = mp[v][u] = 1;
}
dfs(1, 0);
for(int i = 1, u, v; i <= m; ++i) {
cin >> u >> v;
add(u, v);
}
cout << ans;
return 0;
}