首页 > 其他分享 >CSP-S 2022~2023 补题

CSP-S 2022~2023 补题

时间:2024-09-28 16:50:00浏览次数:1  
标签:le int text LL cin 补题 2022 CSP rightarrow

下面的代码都是远古代码,不打算重写了。

CSP-S 2023

T1 密码锁

题意:一个密码锁上有 \(5\) 个位置,每个位置上的数字是 \(0 \sim 9\) 的循环,每次打乱会选择一或两个相邻位置同时循环移动。
给定 \(n\) 个打乱后的状态,求有多少种可能的原状态。
\(n \le 8\)。

容易发现可能的状态数只有 \(10^5\) 种,全部搜出来,然后一一暴力校验合不合法即可。

Code
#include <iostream>

using namespace std;

const int N = 9; 

int n, ans;
int a[N][6], b[6];
int dif[6], cnt;

bool check (int *a, int *b) { 
  cnt = 0;
  for (int i = 1; i <= 5; ++i) {
    if (a[i] != b[i]) {
      dif[++cnt] = i;
    }
  }
  if (!cnt || (cnt == 2 && dif[1] + 1 != dif[2]) || cnt > 2) return 0;  
  if (cnt == 1) return 1; 
  return !((a[dif[1]] + b[dif[2]] - a[dif[2]] - b[dif[1]]) % 10);
}

void dfs (int x) {
  if (x == 6) {
    bool fl = 1;
    for (int i = 1; fl && i <= n; ++i) {
      fl &= check(a[i], b);
    }
    ans += fl;
    return; 
  }
  for (int i = 0; i <= 9; ++i) {
    b[x] = i, dfs(x + 1);
  }
}

int main () {
  ios::sync_with_stdio(0);
  cin.tie(0); cout.tie(0);
  cin >> n;
  for (int i = 1; i <= n; ++i) {
    for (int j = 1; j <= 5; ++j) {
      cin >> a[i][j];
    }
  }
  dfs(1);
  cout << ans << '\n';
  return 0; 
}

T2 消消乐

我们称一个字符串 \(a\) 是可消除的,当且仅当每次删去 \(a\) 中相邻的两个字符,经过若干次操作后能将 \(a\) 删空。
给定一个长度为 \(n\) 的字符串 \(s\),求 \(s\) 有多少个非空子串是可消除的。
\(1 \le n \le 2 \times 10^6\)。

注意到可消除串与括号序列非常相似。我们为每一个字符指定一个左方向或右方向,那么字符串构成一个括号序列(括号的种类可能有很多)。

所以我们引入类似括号序列的判定方法:依次将所有元素压入栈中,若栈顶的两个元素相同则消去,最终如果得到空串,则原串是可消除的。

于是我们有了一个 \(O(n^2)\) 的做法。枚举左端点,然后将后面的元素依次加入,只需计数有多少个时刻栈为空。

进一步发掘可消除串的性质,我们发现在任意栈中加入一个可消除串,这个栈是不会改变的。具体证明考虑还是看成区分左右括号的括号序列,对于一个可消除串先进行括号匹配,对于一对匹配的括号 \(a, b\),若 \(a\) 压入栈中成为左括号则可以直接消除,否则 \(a\) 作为右括号和栈中的一个元素 \(c\) 匹配,此时考虑数学归纳,\([a + 1, b - 1]\) 中的元素也是可消除串,则它们不会使栈该边,则 \(b\) 加入栈时会取代 \(c\),整体还是不变。

我们发现上面的结论反过来也成立,若一个栈加入串 \(a\) 后不变,则 \(a\) 一定是可消除串,证明类似。

所以我们用两端前缀 \([1, l - 1], [1, r]\) 的栈来刻画一个可消除串 \([l, r]\),若 \([1, l - 1]\) 和 \([1, r]\) 的栈相同,则 \([l, r]\) 时可消除的。所以我们按前缀栈对于 \([0, n]\) 中每一个位置划分等价类,对于一个等价类 \(C\),答案为 \(|C| \choose 2\)。等价类的划分可以直接哈希。

时间复杂度 \(O(n)\)。

Code
#include <iostream>
#include <map>

using namespace std;
using Pr = pair<int, int>; 
using LL = long long;

const int N = 2e6 + 5;
const int Mod = 1e9 + 7; 

int n, tp; 
int w[N], v[N];
char s[N], st[N];
int wp[N], vp[N];
map<Pr, int> p; 

int main () {
  ios::sync_with_stdio(0);
  cin.tie(0); cout.tie(0);
  cin >> n;
  w[0] = v[0] = 1; 
  for (int i = 1; i <= n; ++i) {
    cin >> s[i];
    w[i] = (w[i - 1] * 1145141ll) % Mod;
    v[i] = (v[i - 1] * 903607ll) % Mod; 
  }
  p[{0, 0}] = 1; 
  for (int i = 1; i <= n; ++i) {
    if (tp && s[i] == st[tp]) {
      wp[i] = ((wp[i - 1] - 1ll * st[tp] * w[tp]) % Mod + Mod) % Mod;
      vp[i] = ((vp[i - 1] - 1ll * st[tp] * v[tp]) % Mod + Mod) % Mod;
      --tp; 
    }
    else {
      st[++tp] = s[i];
      wp[i] = (wp[i - 1] + 1ll * st[tp] * w[tp]) % Mod; 
      vp[i] = (vp[i - 1] + 1ll * st[tp] * v[tp]) % Mod;
    }
    ++p[{wp[i], vp[i]}]; 
  }
  LL ans = 0; 
  for (auto i : p) {
    ans += 1ll * i.second * (i.second - 1) / 2;
  }
  cout << ans << '\n';
  return 0; 
}

T3 结构体

题意:写一个程序,模拟 C++ 中的结构体,变量,和它们的地址。

大模拟。注意仔细读题(特别是对其规则的部分)。

Code
#include <iostream>
#include <vector>
#include <map>

using namespace std;
using LL = long long;

const int N = 1e2 + 10;
const LL Inf = 1e18 + 1;

int n, tot = 4, cnt; 

struct type {
  LL siz, mx; 
  vector<int> e;
} t[N] = {{0, Inf}, {1, 1}, {2, 2}, {4, 4}, {8, 8}}; 
map<string, int> p{{"byte", 1}, {"short", 2}, {"int", 3}, {"long", 4}};
struct element {
  int ty; 
  string na; 
} a[N * N];
string ans; 

void align (LL &x, LL y) {
  if (x % y) x += y - x % y; 
}

LL query_id (int k, string s) {
  if (s.empty() || a[k].ty && a[k].ty <= 4) return 0; 
  string p = "";
  for (int i = 0; i < s.size(); ++i) {
    if (s[i] == '.') break;
    p += s[i];
  }
  LL res = 0; 
  for (auto i : t[a[k].ty].e) {
    align(res, t[a[i].ty].mx);
    if (a[i].na == p) {
      return res + query_id(i, s.substr(p.size() + 1, s.size() - p.size() - 1));
    }
    res += t[a[i].ty].siz;
  } 
}

void query_s (int k, LL x) { 
  if (a[k].ty && t[a[k].ty].e.empty() && x <= t[a[k].ty].siz) {
    ans.pop_back();
    return; 
  } 
  LL now, nex = 0; 
  for (auto i : t[a[k].ty].e) {
    align(now = nex, t[a[i].ty].mx); 
    nex = now + t[a[i].ty].siz; 
    if (x >= now && x < nex) {
      ans += a[i].na + ".";
      return (void)query_s(i, x - now);
    } 
  }
  ans = "ERR";
} 

int main () {
  ios::sync_with_stdio(0);
  cin.tie(0); cout.tie(0);
  cin >> n;
  for (int o; n--; ) {
    cin >> o; 
    string s; 
    if (o == 1) {
      cin >> s;
      p[s] = ++tot; 
      int l; cin >> l; 
      for (int i = 1; i <= l; ++i) {
        cin >> s;
        a[++cnt].ty = p[s];
        cin >> a[cnt].na;
        t[tot].e.push_back(cnt);
        t[tot].mx = max(t[tot].mx, t[a[cnt].ty].mx);
      }
      for (auto i : t[tot].e) {
        align(t[tot].siz, t[a[i].ty].mx);
        t[tot].siz += t[a[i].ty].siz;
      }
      align(t[tot].siz, t[tot].mx);
      cout << t[tot].siz << ' ' << t[tot].mx << '\n';
    }
    else if (o == 2) {
      cin >> s; 
      a[++cnt].ty = p[s];
      cin >> a[cnt].na;
      t[0].e.push_back(cnt);
      align(t[0].siz, t[a[cnt].ty].mx);
      cout << t[0].siz << '\n';
      t[0].siz += t[a[cnt].ty].siz;
    }
    else if (o == 3) {
      cin >> s;
      cout << query_id(0, s + ".") << '\n';
    }
    else {
      LL x; cin >> x;
      ans = "", query_s(0, x);
      cout << ans << '\n';
    }
  }
  return 0; 
}

T4 种树

题意:有 \(n\) 个地块,用 \(n - 1\) 条道路连接,构成一个有根树结构。
你需要在这些地块上种树,第 \(i\) 个地块上的树需要生长到 \(a_i\) 的高度,它在第 \(x\) 天会生长 \(\max(1, b_i + c_ix)\) 的高度。每天只能在一个地块上种树。
并且我们要求种树的顺序必须满足,若将 \(n\) 个地块看成有根树,那么一个点可以种树当且仅当它的所有祖先已经种了树,求最少多少天可以使得 \(\forall i \in [1, n]\),第 \(i\) 棵树长到了 \(a_i\) 的高度。
\(1 \le n \le 10^5, 1 \le a_i \le 10^{18}, 1 \le b_i, c_i \le 10^9\)。

显然正着不太好做。考虑二分答案为 \(w\),问题变成判定 \(w\) 的合法性。

我们希望计算一个 \(t_i\),表示位置 \(i\) 最晚要在第 \(t_i\) 天种树,这样可以转化成一个纯正的树上问题。

然而 \(t_i\) 还是不好计算,我们考虑再二分它,问题就变成对于一个位置 \(i\),算它生长时间为 \([l, r]\) 是否可行。这个我们可以直接用数学方法算出生长的高度,然后和 \(a_i\) 比较即可。

考虑求出 \(t_i\) 后怎么做。显然 \(t_i\) 越小的位置限制越严格,所以我们贪心地处理 \(t_i\) 小的位置,并给每一个位置分配一个种树的日期 \(num_i\),那么一个方案可行当且仅当 \(\forall i \in [1, n], num_i \le t_i\)。

具体而言,我们将所有位置 \(i\) 按照 \(t_i\) 从小到大排序后分别考虑。假设我们当前考虑一个位置 \(i\),并且 \(num_i\) 未确定。我们跳到 \(i\) 的最高级祖先 \(r\) 满足 \(num_r\) 也未确定,然后从 \(r\) 开始向下走,依次贪心分配最小标号即可。

时间复杂度 \(O(n \log n \log V)\)。

Code
#include <iostream>
#include <vector>

using namespace std;
using LL = long long;
using i128 = __int128;

const int N = 1e5 + 5; 

int n, b[N], c[N], t[N], q[N], cnt[N], fa[N], num[N];
LL a[N];
vector<int> e[N];

void Init (int u) {
  for (auto v : e[u]) {
    if (v != fa[u]) {
      fa[v] = u, Init(v);
    }
  }
}

bool Valid (int i, LL l, LL r) {
  i128 d; 
  if (!c[i]) {
    LL p = max(b[i], 1);
    d = i128(p) * (r - l + 1);
  }
  else if (c[i] > 0) {    
    LL p = min(r + 1, max(l, 0ll + (c[i] - b[i]) / c[i])); 
    i128 dy = min(i128(1ll << 60), i128(p + r) * (r - p + 1) / 2);
    d = (p - l) + (i128(b[i]) * (r - p + 1) + dy * c[i]);
  }
  else {
    LL p = max(l - 1, min(r, 0ll + (1 - b[i]) / c[i]));
    d = (r - p) + (i128(b[i]) * (p - l + 1) + i128(p + l) * (p - l + 1) / 2 * c[i]);
  }
  return d >= a[i];
}

bool Check (LL w) {
  for (int i = 1; i <= n; ++i) {
    LL l = 1, r = n;
    while (l <= r) {
      LL mid = (l + r) >> 1;
      if (Valid(i, mid, w)) {
        l = mid + 1; 
      }
      else {
        r = mid - 1;
      }
    }
    t[i] = r;
    if (!t[i]) return 0; 
  }
  fill(cnt, cnt + n + 1, 0);
  for (int i = 1; i <= n; ++i) {
    ++cnt[t[i]];
  }
  for (int i = 1; i <= n; ++i) {
    cnt[i] += cnt[i - 1];
  }
  for (int i = 1; i <= n; ++i) {
    q[cnt[t[i]]--] = i; 
  }
  fill(num, num + n + 1, 0);
  int cur = 0; 
  bool fl = 1; 
  for (int i = 1; i <= n; ++i) {
    int u = q[i];
    if (num[u]) continue;
    vector<int> vec;
    for (int v = u; v && !num[v]; v = fa[v]) {
      vec.push_back(v);
    }
    while (!vec.empty()) {
      int v = vec.back();
      vec.pop_back();
      num[v] = ++cur;
    }
    fl &= (num[u] <= t[u]);
  }
  return fl;
}

int main () {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n;
  LL lim = 0; 
  for (int i = 1; i <= n; ++i) {
    cin >> a[i] >> b[i] >> c[i];
    LL l = n, r = LL(1e18) + n;
    while (l <= r) {
      LL mid = (l + r) >> 1;
      if (Valid(i, n, mid)) {
        r = mid - 1;
      }
      else {
        l = mid + 1; 
      }
    }
    lim = max(lim, l);
  }
  for (int i = 1, u, v; i < n; ++i) {
    cin >> u >> v;
    e[u].push_back(v);
    e[v].push_back(u);
  }
  Init(1);
  LL l = n, r = lim;
  while (l <= r) {
    LL mid = (l + r) >> 1;
    if (Check(mid)) {
      r = mid - 1; 
    }
    else {
      l = mid + 1; 
    }
  }
  cout << l << '\n';
  return 0; 
}

CSP-S 2022

T1 假期计划

题意:给定一张 \(n\) 个顶点 \(m\) 条边的图,\(1\) 是家,\(2 \sim n\) 是景点,每个景点有一个价值 \(v_i\)。
要求选 \(4\) 个不同的景点 \(x_1, x_2, x_3, x_4\) 使得它们的总价值最大,要求五段行程 \(1 \rightarrow x_1, x_1 \rightarrow x_2, x_2 \rightarrow x_3, x_3 \rightarrow x_4, x_4 \rightarrow 1\) 中的每段 \(u \rightarrow v\),都需要满足存在一条 \(u\) 到 \(v\) 的路径,且 \(u\) 到 \(v\) 的最短路径长度 \(\le k + 1\)。
求这个最大总价值。
\(1 \le n \le 2.5 \times 10^3, 1 \le m \le 10^4\)。

考虑先从每个点开始 \(\text{bfs}\) 一遍,求出任意两点的最短路 \(dis(u, v)\),对于 \(dis(u, v) \le k + 1\) 的点对连一条边构成一张新图。题意转化为求经过 \(1\) 的最大权五元环。

假设答案形如 \(1 \rightarrow x_1 \rightarrow x_2 \rightarrow x_3 \rightarrow x_4 \rightarrow 1\)。最朴素暴力就是枚举 \(x_1, x_2, x_3, x_4\),时间复杂度 \(O(n^4)\)。

接下来考虑类似 \(\text{Meet in the middle}\),我们枚举 \(x_2, x_3\),考虑一种贪心是选存在边 \((1, v), (v, x_2)\) 的权值最大的 \(v\) 作为 \(x_1\),\(x_4\) 同理,这样显然可以保证权值和最大。

但直接这样做会出问题。假设 \(x_2\) 选出了一个 \(x_1\),\(x_3\) 选出了一个 \(x_4\),有可能出现 \(x_1 = x_3, x_1 = x_4, x_2 = x_4\) 这三种不合法情况。所以我们在位置 \(x_2\) 时保留前 \(4\) 大的 \(x_1\) 即可,\(x_4\) 的选择同理。

Code
#include <bits/stdc++.h>

using namespace std;
using ll = long long;

const int cosk = 4;
const int N = 2505;

int n, m, k;
ll a[N];
vector<int> w[N];

int dis[N];
bool ed[N][N];
vector<int> e[N];

void Bfs (int s) {
  fill(dis, dis + n + 1, N);
  queue<int> Q;
  dis[s] = 0; Q.push(s);
  while (!Q.empty()) {
    int x = Q.front();
    Q.pop();
    for (auto y : e[x]) {
      if (dis[y] == N) {
        dis[y] = dis[x] + 1;
        Q.push(y);
      }
    }
  }
}

void bsort (vector<int> &v) {
  for (int k = min(cosk, (int)v.size() - 1); k > 0; ) {
    if (a[v[k]] > a[v[k - 1]]) {
      swap(v[k], v[k - 1]);
      k--;
    }
    else break;
  }
}

int main () { 
  cin >> n >> m >> k;
  for (int i = 2; i <= n; i++) {
    cin >> a[i];
  }
  for (int i = 1, x, y; i <= m; i++) {
    cin >> x >> y;
    e[x].push_back(y);
    e[y].push_back(x);
  }
  for (int i = 1; i <= n; i++) {
    Bfs(i);
    for (int j = 1; j <= n; j++) {
      if (j != i && dis[j] <= k + 1) {
        ed[i][j] = 1;
      }
    }
  }
  for (int i = 2; i <= n; i++) {
    for (int j = 2; j <= n; j++) {
      if (i != j && ed[i][j] && ed[1][j]) {
        if (w[i].size() <= cosk) {
          w[i].push_back(j);
        }
        else {
          w[i][cosk] = j;
        }
        bsort(w[i]);
      }
    }
  }
  ll ans = LONG_LONG_MIN;
  for (int i = 2; i <= n; i++) {
    for (int j = 2; j <= n; j++) {
      if (i != j && ed[i][j]) {
        for (auto x : w[i]) {
          for (auto y : w[j]) {
            if (i != y && j != x && x != y) {
              ans = max(ans, a[i] + a[j] + a[x] + a[y]);
            }
          }
        }
      }
    }
  } 
  cout << ans;
  return 0;
}

T2 策略游戏

给定一个长度为 \(n\) 的数组 \(a\) 和一个长度为 \(m\) 的数组 \(b\),有 \(q\) 次询问:

  • 给定 \(l_1, r_1, l_2, r_2\),现在 \(\text{Alice}\) 会先选一个 \(i \in [l_1, r_1]\),然后 \(\text{Bob}\) 会选一个 \(j \in [l_2, r_2]\)。此时得分为 \(a_i \times b_j\)。
  • \(\text{Alice}\) 想让得分尽量大,\(\text{Bob}\) 想让得分尽量小。假设双方都用最优策略,求得分是多少。

\(1 \le n, m, q \le 10^5\)。

考虑贪心。我们站在 \(\text{Alice}\) 的角度,看她第一步会如何选择:

  • 若 \([l_1, r_1]\) 中存在正数,而 \([l_2, r_2]\) 中只有正数,则 \(\text{Alice}\) 的一种策略是选 \([l_1, r_1]\) 中的最大正数,而 \(\text{Bob}\) 会选 \([l_2, r_2]\) 中的最小正数。
  • 若 \([l_1, r_1]\) 中存在负数,而 \([l_2, r_2]\) 中只有负数,则 \(\text{Alice}\) 的一种策略是选 \([l_1, r_1]\) 中绝对值最大的负数,而 \(\text{Bob}\) 会选 \([l_2, r_2]\) 中绝对值最小的正数。
  • 若 \([l_1, r_1]\) 中有 \(0\),则 \(\text{Alice}\) 的一种策略是直接选 \(0\)。
  • 若 \([l_1, r_1]\) 中有正数,且 \([l_2, r_2]\) 中的最小值为 \(0\)。或 \([l_1, r_1]\) 中有负数,且 \([l_2, r_2]\) 中的最大值为 \(0\)。则 \(\text{Alice}\) 可以逼 \(\text{Bob}\) 选 \(0\)。
  • 若 \([l_1, r_1]\) 中存在正数,则 \(\text{Alice}\) 的一种策略是选最小的正数。
  • 若 \([l_1, r_1]\) 中存在负数,则 \(\text{Alice}\) 的一种策略是选绝对值最小的负数。

可以证明答案一定是上面的情况之一。对于 \(\text{Alice}\) 的策略取 \(\max\) 即可。

接下来就是 \(\text{RMQ}\) 了,这个大家都会。

时间复杂度 \(O(n \log n + q)\)。

Code
#include <iostream>
#include <cmath>

using namespace std;
using LL = long long;

const int N = 1e5 + 5;
const int M = 18;

int n, m, q;
int x[N], y[N];

struct ST {
  int len; 
  int a[N], v[N], Min[N][M], Max[N][M];

  void Input (int *arr) {
    for (int i = 1; i <= len; i++) {
      a[i] = arr[i];
      Min[i][0] = a[i];
      Max[i][0] = a[i];
      v[i] = v[i - 1] + (a[i] == 0);
    }
  }

  void Init () {
    for (int k = 1; k < M; k++) {
      for (int i = 1; i + (1 << k) - 1 <= n; i++) {
        Min[i][k] = min(Min[i][k - 1], Min[i + (1 << (k - 1))][k - 1]);
        Max[i][k] = max(Max[i][k - 1], Max[i + (1 << (k - 1))][k - 1]);
      }
    }
  }

  bool Fzero (int l, int r) {
    return (v[r] - v[l - 1] > 0);
  }

  int Query_Max (int l, int r) {
    int k = log2(r - l + 1);
    return max(Max[l][k], Max[r - (1 << k) + 1][k]);
  }

  int Query_Min (int l, int r) {
    int k = log2(r - l + 1);
    return min(Min[l][k], Min[r - (1 << k) + 1][k]);
  }
} a, b, c, d;

int main () {
  ios::sync_with_stdio(0);
  cin.tie(0); cout.tie(0);
  cin >> n >> m >> q;
  a.len = n, b.len = m;
  for (int i = 1; i <= n; i++) {
    cin >> x[i];
  }
  for (int i = 1; i <= m; i++) {
    cin >> y[i];
  }
  a.Input(x), b.Input(y);
  a.Init(), b.Init();
  c.len = n, d.len = n;
  for (int i = 1; i <= n; i++) {
    y[i] = x[i];
    if (x[i] <= 0) {
      x[i] = 1e9;
    }
    if (y[i] >= 0) {
      y[i] = -1e9;
    }
  }
  c.Input(x), d.Input(y); 
  c.Init(), d.Init();
  for (int al, ar, bl, br; q--; ) {
    cin >> al >> ar >> bl >> br;
    LL ans = -1e18;
    int aMax = a.Query_Max(al, ar);
    int aMin = a.Query_Min(al, ar);
    int bMax = b.Query_Max(bl, br);
    int bMin = b.Query_Min(bl, br);
    if (aMax > 0 && bMin > 0) {
      ans = max(ans, 1ll * aMax * bMin);
    }
    if (aMin < 0 && bMax < 0) {
      ans = max(ans, 1ll * aMin * bMax);
    }
    if (a.Fzero(al, ar) || (aMax > 0 && bMin == 0) || (aMin < 0 && bMax == 0)) {
      ans = max(ans, 0ll);
    }
    if (aMax > 0 && bMin < 0) {
      ans = max(ans, 1ll * c.Query_Min(al, ar) * bMin);
    }
    if (aMin < 0 && bMax > 0) {
      ans = max(ans, 1ll * d.Query_Max(al, ar) * bMax);
    }
    cout << ans << '\n';
  }
  return 0;
}

T3 星战

给定一张 \(n\) 个点 \(m\) 条边的有向图,每条边都有激活和失活两种状态,初始时所有边都为激活。有四种操作共 \(q\) 次:

  1. 激活某条边。
  2. 激活以某个点作为终点的所有边。
  3. 失活某条边。
  4. 失活以某个点作为终点的所有边。

然后每次操作后询问,如果只考虑激活的边,是否满足:

  • 所有点出度均为 \(1\)。
  • 所有的点都满足,从这个点出发,可以走到一个环中。

\(1 \le n, m \le 5 \times 10^5\)。

首先题目中的条件等价为所有点出度为 \(1\)。因为此时你从一个点开始不断走出边,肯定是能到环的。

但是我们发现题目中的 \(2\) 和 \(4\) 操作很难直接维护出度。所以考虑哈希。

具体而言,我们称一条边的权值 \((u, v)\) 为 \(u\),那么你可以近似的认为,当所有激活边的权值之和为 \(\frac{(n + 1)n}{2}\) 时,所有点出度就是 \(1\)。

此时对于单边的修改仍然是容易的,现在考虑我们修改以 \(v\) 为终点的所有边的状态。若此时所有边的状态是一样的话,我们可以维护 \(s_v\) 表示所有以 \(v\) 为终点的边的权值和。

那如果有些边的状态会被单点修改呢?也很好办,在做单点修改的时候更新 \(s_u\) 就好了。

最后我们可以将前面所述一条边 \((u, v)\) 的权值修改为与 \(u\) 有关的任意随机函数 \(f(u)\),对后面的步骤不影响,这样可以保证正确性。

时间复杂度 \(O(n + m + q)\)。

Code
#include <iostream>
#include <numeric>

using namespace std;
using LL = long long;

const int N = 5e5 + 5;

int n, m, q;
int w[N];
LL r[N], s[N], sum, now;

int main () {
  ios::sync_with_stdio(0);
  cin.tie(0); cout.tie(0);
  cin >> n >> m;
  for (int i = 1; i <= n; ++i) {
    w[i] = rand() * rand();
    sum += w[i];
  }
  for (int i = 1; i <= m; ++i) {
    int u, v;
    cin >> u >> v;
    s[v] = (r[v] += w[u]); 
  }
  now = accumulate(r + 1, r + n + 1, 0ll);
  cin >> q;
  while (q--) {
    int o, u, v;
    cin >> o >> u;
    if (o == 1) {
      cin >> v;
      r[v] -= w[u], now -= w[u];
    }
    if (o == 2) {
      now -= r[u], r[u] = 0;
    }
    if (o == 3) {
      cin >> v;
      r[v] += w[u], now += w[u];
    }
    if (o == 4) {
      now += s[u] - r[u], r[u] = s[u];
    }
    cout << (now == sum ? "YES" : "NO") << '\n';
  }
  return 0; 
}

T4 数据传输

题意:给定一棵 \(n\) 个结点的树和一个整数 \(k\),第 \(i\) 个点有权值 \(v_i\),\(q\) 次询问;

  • 给出两个点 \(s\) 和 \(t\),你需要选出一个顶点序列 \(c_1 = s, c_2, c_3, \ldots, c_{m - 1}, c_m = t\),满足 \(\forall i \in [1, n)\),\(c_i\) 和 \(c_{i + 1}\) 在树上的距离不超过 \(k\),最小化 \(\sum\limits_{i = 1}^{m} v_{c_i}\)。

\(1 \le n, q \le 2 \times 10^5, k \le 3, v_i \ge 1\)。

先考虑 \(k = 3\) 的情况。对于一次询问 \(s, t\),我们将 \(s\) 到 \(t\) 这条路径拉出来,写成 \(s \rightarrow u_1 \rightarrow u_2 \rightarrow u_3 \rightarrow \ldots \rightarrow u_m \rightarrow t\)。

观察最终的点序列长什么样,对于一个点 \(v\),若它到这条路径的距离 \(\ge 2\),则我们断言它一定不会用到。因为若存在 \(c_i \rightarrow v \rightarrow c_{i + 2}\),则显然可以直接从 \(c_i\) 走到 \(c_{i + 2}\),而这题点权为正,所以这样一定不优。

所以对于一个路径上的点 \(u_i\),我们只关系它点权最小的,且不在路径上的邻居 \(t\)。甚至我们连这个邻居是什么也不关心,而只关心它的点权 \(w_{u_i} = v_t\)。

而每次询问求出 \(w_{u_i}\) 显然是不现实的。我们发现 \(t\) 在不在路径上无所谓,因为如果 \(t\) 在路径上只会更优,这显然合法。所以我们对于每个点 \(i\),预处理它的点权最小的邻居,权值记作 \(w_i\)。

然后我们开始 \(\text{DP}\),设 \(f_{j}\) 表示我们考虑了路径上的一段前缀 \(s \rightarrow \ldots \rightarrow u_i\),选出的最后一个点距离 \(u_i\) 为 \(j\) 的方案数,显然 \(j = 0/1/2\)。这样可以 \(O(1)\) 转移,我们就得到时间复杂度为 \(O(nq)\) 的算法。

正解也很简单,考虑将 \(\text{DP}\) 的过程写成 \((\min, +)\) 矩阵乘法的形式。然后树上倍增维护一条路径上转移矩阵的并即可。

然后 \(k = 1/2\) 的情况显然非常简单。并且在写完 \(k = 3\) 后,也可以直接通过修改转移矩阵的方式通过。

时间复杂度 \(O((n + q) \log n)\),此处忽略矩阵乘法的常数。

Code
#include <iostream>
#include <vector>

using namespace std;
using LL = long long;

const int N = 2e5 + 5;
const int M = 19;
const LL Inf = 1e18;

struct Mat {
  LL a[3][3];

  void clear () { fill(a[0], a[2] + 3, Inf); }
  void init () { clear(); a[0][0] = a[1][1] = a[2][2] = 0; }
};

Mat operator* (Mat A, Mat B) {
  Mat res; res.clear();
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
      for (int k = 0; k < 3; k++) {
        res.a[i][j] = min(res.a[i][j], A.a[i][k] + B.a[k][j]);
      }
    }
  }
  return res;
}

int n, m, k;
LL num[N], val[N];
int fa[N][M], dep[N];
Mat p[N][M], s[N][M];
vector<int> e[N];

Mat Init (int u) {
  Mat res; res.clear();
  res.a[0][0] = val[u];
  if (k >= 2) {
    res.a[0][1] = val[u], res.a[1][0] = 0; 
  }
  if (k >= 3) {
    res.a[0][2] = val[u], res.a[2][1] = 0, res.a[1][1] = num[u];
  }
  return res;
}

void Dfs (int u) {
  dep[u] = dep[fa[u][0]] + 1;
  num[u] = Inf;
  for (auto v : e[u]) {
    num[u] = min(num[u], val[v]);
    if (v != fa[u][0]) {
      fa[v][0] = u;
      Dfs(v);
    }
  }
  p[u][0] = s[u][0] = Init(u);
}

Mat Gets (int u, int v) {
  Mat us, vs; us.init(), vs.init();
  if (dep[u] < dep[v]) {
    vs = p[v][0], v = fa[v][0];
  }
  for (int k = M - 1; k >= 0; k--) {
    if (dep[u] - dep[v] >= (1 << k)) {
      us = s[u][k] * us;
      u = fa[u][k];
    }
  }
  if (u == v) return vs * p[u][0] * us;
  for (int k = M - 1; k >= 0; k--) {
    if (fa[u][k] != fa[v][k]) {
      us = s[u][k] * us, u = fa[u][k];
      vs = vs * p[v][k], v = fa[v][k];
    }
  }
  return vs * p[v][1] * p[u][0] * us;
}

LL Query (int u, int v) {
  if (u == v) return val[u];
  if (dep[u] < dep[v]) swap(u, v);
  Mat trs = Gets(fa[u][0], v);
  Mat base; base.clear();
  base.a[0][0] = val[u];
  return (trs * base).a[0][0];
}

int main () {
  ios::sync_with_stdio(0);
  cin.tie(0); cout.tie(0); 
  cin >> n >> m >> k;
  for (int i = 1; i <= n; i++) {
    cin >> val[i];
  }
  for (int i = 1; i < n; i++) {
    int u, v;
    cin >> u >> v;
    e[u].push_back(v);
    e[v].push_back(u);
  }
  Dfs(1);
  for (int k = 1; k < M; k++) {
    for (int i = 1; i <= n; i++) {
      fa[i][k] = fa[fa[i][k - 1]][k - 1];
      p[i][k] = p[i][k - 1] * p[fa[i][k - 1]][k - 1];
      s[i][k] = s[fa[i][k - 1]][k - 1] * s[i][k - 1]; 
    }
  }
  while (m--) {
    int u, v;
    cin >> u >> v;
    cout << Query(u, v) << '\n';
  } 
  return 0; 
}

标签:le,int,text,LL,cin,补题,2022,CSP,rightarrow
From: https://www.cnblogs.com/hztmax0/p/18436963

相关文章

  • [DMY]2024 CSP-S 模拟赛 Day 6
    前言离A掉一道题最近的一次。过程看完T1以后,想先构一个\(\mathcal{O}(n^3)\)的暴力,但是发现只有10pts,而且算法复杂度接近\(\mathcal{O}(n^4)\),所以果断放弃。把链的限制写了(然后亏大了),然后开始在CS上面画画。画了一会发现一个简单的dp思路,但是是\(\mathcal{O}(n^......
  • CSP & NOIP 模拟赛记录
    9.18(lanos212)T1签到,10mins内过了。T2乍一看有点困难,题目太形式化,不太直观,先跳过去思考T3了,T3没有什么DP的思路,但是这种题显然需要DP。看了一眼T4,被一堆式子糊脸恶心了,没有怎么思考。接下来一个小时在思考T2和T3,突然发现T2只需要每次求最短路就可以了,那么就是......
  • 信息学奥赛复赛复习06-CSP-J2020-02直播获奖-向上取整、向下取整、整数除法、最大值、
    PDF文档公众号回复关键字:2024092812020CSP-J题目1优秀的拆分[题目描述]NOI2130即将举行。为了增加观赏性,CCF决定逐一评出每个选手的成绩,并直播即时的获奖分数线。本次竞赛的获奖率为w%,即当前排名前w%的选手的最低成绩就是即时的分数线更具体地,若当前已评出了p个......
  • 第一章数据管理【4’】(DAMA-CDGA 2022年以后历年模拟题真题汇总,基本包含所有考点。)
    1、以下哪个不是DAMA-DMBOK的数据管理框架图?(知识点:第一章数据管理)A.DAMA车轮图B.DMBOK金字塔图C.环境因素六边形图D.知识领域语境关系图参考答案:B题目解析:DMBOK2第一章数据管理1.3.3,DAMA-DMBOK框架2、以下关于数据管理原则描述正确的是?(知识点:第一章数据管理)A.......
  • 第二章数据伦理处理【2’】(DAMA-CDGA 2022年以后历年模拟题真题汇总,基本包含所有考点
    1、数据伦理处理在业务驱动中有供给者、参与者、消费者三方共同构成,以下哪个成员不属于消费者一方?(知识点:第二章数据伦理处理)A.员工B.管理人员C.监管部门D.变更经理参考答案:D题目解析:P29.数据伦理处理语境关系图2、在数据处理伦理的活动中,下列哪项不属于活动之一?(知......
  • 第三章数据治理【10’】(DAMA-CDGA 2022年以后历年模拟题真题汇总,基本包含所有考点。)
    1、数据治理的驱动因素大多聚焦于减少风险或者改进流程,以下关于改进流程的描述中哪项不正确:(知识点:第三章数据治理)A.有效和持续地响应监管要求B.通过数据的完整一致性提升业务绩效C.定义和定位组织中的数据,确保元数据被管理和应用D.利用数据全周期治理来管理特定数据的......
  • ISO/IEC/IEEE 29119-1:2022(E) 系统与软件工程软件测试第1部分:概念和定义
    0前言国际标准化组织(ISOtheInternationalOrganizationforStandardization)和国际电工委员会(IECtheInternationalElectrotechnicalCommission)构成了世界标准化的专门体系。作为国际标准化组织或国际电工委员会成员的国家机构通过各自组织设立的技术委员会参与国际标准的......
  • 2024csp初赛总结
    浙江27日下午1:30出分了,j组97,s组61.5,和估分一模一样,还好没有挂分。然后3点的时候上洛谷看了一下,全国分数线出了,j组89分,s组56分。那应该都过了,随后同学的成绩也出来了,sjx,yxs,tdq应该也都过了,皆大欢喜。以比赛日2024.09.21为DAY0.DAY-8(9.13)从常州回来了,到家已经挺晚的了,洗漱了一......
  • 『模拟赛』CSP-S模拟5
    Rank一般A.光胱!妈的传奇题目控我两个小时想\(\mathcal{O(1)}\)做法。其实带下取整的四个四元一次方程根本解不了,考虑到这个题给的范围只有\(n\le1500\),可以枚举两维剩下二分到一个小区间里去做,复杂度\(\mathcal{O(n^2\logn)}\),当然数据水卡卡枚举的边界\(\mathcal......
  • CSP2024-27
    2A题意:1A题意:给定\(n\timesn\)种物品,\((i,j)\)有\(a_{i,j}\)个,权值为\(b_{i,j}\),两个物品等价当且仅当\(i\)相等或\(j\)相等。初始有一个空(可重)集\(S\),每次等概率从剩余物品中选一个\(x\)出来。如果\(S\)中没有和\(x\)等价的物品,那么\(x\)加入\(S\)......