逐月信息学 2024 提高组 #3

\(\color{black}\texttt{A. 反转Dag图}\)

题目描述

给定一个有向图,每次操作可以花费 \(w_i\) 的代价来反转边 \(i\),最终总代价为每次操作代价的最大值。求最少需要多少代价才能使这张图变为一个 DAG。

思路

首先看这个问题的简化版:把反转操作变为删除操作。

可以用二分解决:二分出最终答案 \(x\),把所有代价 \(\le x\) 的边删掉,再拓扑排序判断即可。

这时可以发现,两个问题竟然是等价的:假设有 \(u\rightarrow v\) 这条边,如果 \(u\) 的拓扑序小于 \(v\),则这条边不用删除或翻转;否则这条边必须删除或翻转。即要删除一定会翻转,反之亦然。

代码

#include<bits/stdc++.h>
using namespace std;
using pii = pair<int, int>;

const int MAXN = 100001, MAXM = 100005;

struct Edge {
  int v, w;
};

int n, m, in[MAXN];
vector<Edge> e[MAXN];

void FileIO(const string &s) {
  freopen((s + ".in").c_str(), "r", stdin);
  freopen((s + ".out").c_str(), "w", stdout);
}

bool check(int x) {
  queue<int> que;
  fill(in + 1, in + n + 1, 0);
  for(int i = 1; i <= n; ++i) {
    for(auto [v, w] : e[i]) {
      in[v] += (w > x);
    }
  }
  for(int i = 1; i <= n; ++i) {
    if(!in[i]) {
      que.push(i);
    }
  }
  for(; !que.empty(); ) {
    int u = que.front();
    que.pop();
    for(auto [v, w] : e[u]) {
      if(w > x && !(--in[v])) {
        que.push(v);
      }
    }
  }
  for(int i = 1; i <= n; ++i) {
    if(in[i]) {
      return 0;
    }
  }
  return 1;
}

int Binary_Search() {
  int l = 0, r = 1000000001;
  for(; l < r; ) {
    int mid = (l + r) >> 1;
    (check(mid) ? r = mid : l = mid + 1);
  }
  return l;
}

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  FileIO("antidag");
  cin >> n >> m;
  for(int i = 1, u, v, w; i <= m; ++i) {
    cin >> u >> v >> w;
    e[u].push_back({v, w});
  }
  cout << Binary_Search();
  return 0;
}

\(\color{black}\texttt{B. 歪脖子树}\)

题目描述

给定一个根初始为 \(1\) 的树,每个点都有一个权值 \(A_i\),有 \(Q\) 次操作。每次操作为以下的一个:

  • 将点 \(x\) 的权值改为 \(y\)
  • 将根重新定义为 \(x\)
  • 查询 \(x\) 子树内权值最小值。

对每个三操作给出答案。

思路

分类讨论+树链剖分。

对于每个三操作:

  • 如果此时根在 \(x\) 的父亲子树内,则答案不变。
  • 如果根就是 \(x\),则答案为整棵树的最小值。
  • 如果根在 \(x\) 的某个儿子的子树内,则答案为除那个儿子子树外所有点最小值。

代码

#include<bits/stdc++.h>
using namespace std;

const int MAXN = 100001;

struct Segment_Tree {
  int l[4 * MAXN], r[4 * MAXN], Min[4 * MAXN], dfn[MAXN], a[MAXN];
  void build(int u, int s, int t) {
    l[u] = s, r[u] = t;
    if(s == t) {
      Min[u] = a[dfn[s]];
      return;
    }
    int mid = (s + t) >> 1;
    build(2 * u, s, mid), build(2 * u + 1, mid + 1, t);
    Min[u] = min(Min[2 * u], Min[2 * u + 1]);
  }
  void update(int u, int p, int x) {
    if(l[u] == r[u]) {
      Min[u] = x;
      return;
    }
    (p <= r[2 * u] ? update(2 * u, p, x) : update(2 * u + 1, p, x));
    Min[u] = min(Min[2 * u], Min[2 * u + 1]);
  }
  int Get(int u, int s, int t) {
    if(s > t) {
      return INT_MAX;
    }
    if(l[u] >= s && r[u] <= t) {
      return Min[u];
    }
    int x = INT_MAX;
    if(s <= r[2 * u]) {
      x = min(x, Get(2 * u, s, t));
    }
    if(t >= l[2 * u + 1]) {
      x = min(x, Get(2 * u + 1, s, t));
    }
    return x;
  }
}tr;

int n, q, f[MAXN][18], a[MAXN], ROOT = 1, dfn[MAXN], sz[MAXN], dep[MAXN], tot;
vector<int> e[MAXN];

void FileIO(const string &s) {
  freopen((s + ".in").c_str(), "r", stdin);
  freopen((s + ".out").c_str(), "w", stdout);
}

void dfs(int u, int fa) {
  dfn[u] = ++tot, tr.dfn[tot] = u, sz[u] = 1, dep[u] = dep[fa] + 1;
  for(int i = 1; i <= 17; ++i) {
    f[u][i] = f[f[u][i - 1]][i - 1];
  }
  for(int v : e[u]) {
    if(v != fa) {
      dfs(v, u);
      sz[u] += sz[v];
    }
  }
}

int Get(int u, int v) {
  int d = dep[v] - dep[u] - 1;
  for(int i = 17; i >= 0; --i) {
    if((d >> i) & 1) {
      v = f[v][i];
    }
  }
  return v;
}

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  FileIO("tree");
  cin >> n >> q;
  for(int i = 1; i <= n; ++i) {
    cin >> f[i][0] >> tr.a[i];
    if(f[i][0]) {
      e[f[i][0]].push_back(i), e[i].push_back(f[i][0]);
    }
  }
  dfs(1, 0);
  tr.build(1, 1, n);
  for(int i = 1, x, y; i <= q; ++i) {
    char c;
    cin >> c >> x;
    if(c == 'V') {
      cin >> y;
      tr.update(1, dfn[x], y);
    }else if(c == 'E') {
      ROOT = x;
    }else {
      if(ROOT == x) {
        cout << tr.Get(1, 1, n) << "\n";
      }else if(dfn[ROOT] < dfn[x] || dfn[ROOT] >= dfn[x] + sz[x]) {
        cout << tr.Get(1, dfn[x], dfn[x] + sz[x] - 1) << "\n";
      }else {
        int u = Get(x, ROOT);
        cout << min(tr.Get(1, 1, dfn[u] - 1), tr.Get(1, dfn[u] + sz[u], n)) << "\n";
      }
    }
  }
  return 0;
}

\(\color{black}\texttt{C. 倒水问题}\)

题目描述

\(N\) 个水杯,每个水杯有 \(a_i\) 升水,令当前最大水量为 \(G\),你可以将所有水量 \(>\lfloor\frac{G}{2}\rfloor\) 的水杯倒空。一开始你可以事先把至多 \(K\) 杯水倒空。问最少需要多少次操作才能把所有水杯倒空。在此基础上找到事先倒的最少次数。

思路

首先将 \(a\) 排序,使用双指针求出每一杯水 \(i\) 最大的 \(l_i\) 使 \(a_{l_i} \le \lfloor \frac{a_i}{2}\rfloor\)

由于总次数 \(\le \log \max \{a_i\}\),所以定义 \(dp_{i,j}\) 表示把前 \(i\) 杯水倒完,使用 \(j\) 次操作一开始最少要倒掉多少杯水,\(dp_{i,j}=\min (dp_{i-1,j}+1,dp_{l_i,j-1})\)

时空复杂度均为 \(O(N\log \max \{a_i\})\)

代码

#include<bits/stdc++.h>
using namespace std;

const int MAXN = 200001, INF = int(1e9) + 1;

int n, k, a[MAXN], dp[MAXN][41], l[MAXN];

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n >> k;
  for(int i = 1; i <= n; ++i) {
    cin >> a[i];
  }
  sort(a + 1, a + n + 1);
  for(int i = n, j = n; i >= 1; --i) {
    for(; j >= 1 && a[j] > a[i] / 2; --j) {
    }
    l[i] = j;
  }
  for(int i = 1; i <= 40; ++i) {
    dp[0][i] = INF;
  }
  dp[0][0] = 0;
  for(int i = 1; i <= n; ++i) {
    for(int j = 0; j <= 40; ++j) {
      dp[i][j] = min(dp[i - 1][j] + 1, (j ? dp[l[i]][j - 1] : INF));
    }
  }
  for(int i = 0; i <= 40; ++i) {
    if(dp[n][i] <= k) {
      cout << i << " " << dp[n][i];
      break;
    }
  }
  return 0;
}

\(\color{black}\texttt{D. 树的颜色}\)

题目描述

给定一颗树,每条边初始为白色。每次有这些操作:

  • \(u\)\(v\) 路径上所有边反色(即黑变白白变黑)。
  • 将恰好有一个端点在 \(u\)\(v\) 路径上的边反色。
  • 查询 \(u\)\(v\) 路径上黑色边的数量。

思路

对于 \(1\) 操作,直接树链剖分即可。
对于 \(2\) 操作,对于重边树剖,轻边打个标记。
对于 \(3\) 操作,对于重边,直接求和,对于轻边,判断两边是否打标记。

代码

#include<bits/stdc++.h>
using namespace std;

const int MAXN = 100001;

struct Segment_Tree {
  int l[4 * MAXN], r[4 * MAXN], sum[4 * MAXN];
  bool lazy[4 * MAXN];
  void build(int u, int s, int t) {
    l[u] = s, r[u] = t, lazy[u] = sum[u] = 0;
    if(s == t) {
      return;
    }
    int mid = (s + t) >> 1;
    build(2 * u, s, mid), build(2 * u + 1, mid + 1, t);
  }
  void tag(int u, bool x) {
    sum[u] = (x ? r[u] - l[u] + 1 - sum[u] : sum[u]), lazy[u] ^= x;
  }
  void pushdown(int u) {
    tag(2 * u, lazy[u]), tag(2 * u + 1, lazy[u]), lazy[u] = 0;
  }
  void update(int u, int s, int t) {
    if(s > t) {
      return;
    }
    if(l[u] >= s && r[u] <= t) {
      tag(u, 1);
      return;
    }
    pushdown(u);
    if(s <= r[2 * u]) {
      update(2 * u, s, t);
    }
    if(t >= l[2 * u + 1]) {
      update(2 * u + 1, s, t);
    }
    sum[u] = sum[2 * u] + sum[2 * u + 1];
  }
  int Get(int u, int s, int t) {
    if(s > t) {
      return 0;
    }
    if(l[u] >= s && r[u] <= t) {
      return sum[u];
    }
    pushdown(u);
    int x = 0;
    if(s <= r[2 * u]) {
      x += Get(2 * u, s, t);
    }
    if(t >= l[2 * u + 1]) {
      x += Get(2 * u + 1, s, t);
    }
    return x;
  }
}tr, tr2;


int n, m, sz[MAXN], f[MAXN], son[MAXN], dfn[MAXN], _dfn[MAXN], top[MAXN], tot;
vector<int> e[MAXN];

void dfs(int u, int fa) {
  sz[u] = 1, f[u] = fa;
  for(int v : e[u]) {
    if(v != fa) {
      dfs(v, u);
      sz[u] += sz[v];
      if(sz[v] > sz[son[u]]) {
        son[u] = v;
      }
    }
  }
}

void DFS(int u, int fa) {
  dfn[u] = ++tot, _dfn[tot] = u;
  if(son[u]) {
    top[son[u]] = top[u], DFS(son[u], u);
  }
  for(int v : e[u]) {
    if(v != fa && v != son[u]) {
      top[v] = v, DFS(v, u);
    }
  }
}

void Xorpath(int u, int v) {
  for(; top[u] != top[v]; ) {
    if(dfn[u] > dfn[v]) {
      tr.update(1, dfn[top[u]], dfn[u]);
      u = f[top[u]];
    }else {
      tr.update(1, dfn[top[v]], dfn[v]);
      v = f[top[v]];
    }
  }
  tr.update(1, min(dfn[u], dfn[v]) + 1, max(dfn[u], dfn[v]));
}

void Xor(int u, int v) {
  for(; top[u] != top[v]; ) {
    if(dfn[u] > dfn[v]) {
      if(son[u]) {
        tr.update(1, dfn[son[u]], dfn[son[u]]);
      }
      tr.update(1, dfn[top[u]], dfn[top[u]]);
      tr2.update(1, dfn[top[u]], dfn[u]);
      u = f[top[u]];
    }else {
      if(son[v]) {
        tr.update(1, dfn[son[v]], dfn[son[v]]);
      }
      tr.update(1, dfn[top[v]], dfn[top[v]]);
      tr2.update(1, dfn[top[v]], dfn[v]);
      v = f[top[v]];
    }
  }
  tr.update(1, min(dfn[u], dfn[v]), min(dfn[u], dfn[v]));
  tr2.update(1, min(dfn[u], dfn[v]), max(dfn[u], dfn[v]));
  if(son[(dfn[u] > dfn[v] ? u : v)]) {
    tr.update(1, max(dfn[u], dfn[v]) + 1, max(dfn[u], dfn[v]) + 1);
  }
}

int pathsum(int u, int v) {
  int ans = 0;
  for(; top[u] != top[v]; ) {
    if(dfn[u] > dfn[v]) {
      ans += tr.Get(1, dfn[top[u]] + 1, dfn[u]) + (tr2.Get(1, dfn[f[top[u]]], dfn[f[top[u]]]) ^ tr.Get(1, dfn[top[u]], dfn[top[u]]));
      u = f[top[u]];
    }else {
      ans += tr.Get(1, dfn[top[v]] + 1, dfn[v]) + (tr2.Get(1, dfn[f[top[v]]], dfn[f[top[v]]]) ^ tr.Get(1, dfn[top[v]], dfn[top[v]]));
      v = f[top[v]];
    }
  }
  return ans + tr.Get(1, min(dfn[u], dfn[v]) + 1, dfn[u]) + tr.Get(1, min(dfn[u], dfn[v]) + 1, dfn[v]);
}

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n;
  for(int i = 1, u, v; i < n; ++i) {
    cin >> u >> v;
    e[u].push_back(v);
    e[v].push_back(u);
  }
  dfs(1, 0);
  top[1] = 1;
  DFS(1, 0);
  cin >> m;
  tr.build(1, 1, n), tr2.build(1, 1, n);
  for(int i = 1, op, u, v; i <= m; ++i) {
    cin >> op >> u >> v;
    if(op == 1) {
      Xorpath(u, v);
    }else if(op == 2) {
      Xor(u, v);
    }else {
      cout << pathsum(u, v) << "\n";
    }
  }
  return 0;
}
posted @ 2024-07-06 14:59  Yaosicheng124  阅读(5)  评论(0编辑  收藏  举报