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)|\)

而加了边,则会变成一个基环树。环以外的部分按照原来的方法处理。而环上必定有一条边不会用到,否则只有这两种情况:

image

像这样绕回自己肯定没有意义。

image-20241002224626126

而像这样兵分两路肯定其中一路更短,也不可能出现。

所以我们可以枚举环上的每条边,并用树状数组维护 \(\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;
}
posted @ 2024-10-01 23:10  Yaosicheng124  阅读(1)  评论(0编辑  收藏  举报