Loading

2024“钉耙编程”中国大学生算法设计超级联赛(1)

发挥相当差,最好笑的是 1h 没写出一个三维偏序、30min 没写出一个字符串哈希。甚至 1h 没意识到组合数式子推错了。

A

我写了点阴间东西。

假设模式串为 ABC,考虑一个形如 ABCABCABC 的东西,如果长度是 \(x\),会贡献 \(x-n+1\) 个子串。

枚举 \(i\),从 \(i\)\(T\) 分成两部分,一部分与 \(S\) 拼一起跑 Z 函数得到 \(T[i \dots n]\) 能与 \(S\) 的前缀匹配几位,再翻转 \(S\)\(T\) 得到 \(T[i-1 \dots 0]\) 能与 \(T\) 的后缀从后面匹配几位,两边拼一起得到 BCABCABCABC.. 类似物的长短。

因为一次考虑 \(S\) 的很多次重复,开始要把 \(S\) 复制到长度不小于 \(T\)

没解绑同步流过的。感觉第一发 Hash 被卡常可能真的是输入的锅。

#include <bits/stdc++.h>
#define LL long long
const int M = 2e7 + 5;
using string = std::string;
std::vector<int> getz(string s) {
  int n = s.length();
  std::vector<int> z(n);
  z[0] = n;
  int l = 0, r = 0;
  for (int i = 1; i < n; i++) {
    z[i] = i <= r ? std::min(r - i + 1, z[i - l]) : 0;
    while (i + z[i] < n && s[z[i]] == s[i + z[i]]) ++z[i];
    if (i + z[i] - 1 > r) l = i, r = i + z[i] - 1;
  }
  return z;
}
int main() {
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  int T; std::cin >> T; while (T--) {
    string s, t;
    std::cin >> s >> t;
    int n0 = s.length(), m = t.length(), n = n0;
    int x = m / n0 + 1;
    s.resize(n = n0 * x);
    for (int i = n0; i < n; i++) {
      s[i] = s[i - n0];
    }
    std::vector<int> z1 = getz(s + '@' + t);
    std::reverse(s.begin(), s.end()), std::reverse(t.begin(), t.end());
    auto z2 = getz(s + '@' + t);
    z1.erase(z1.begin(), z1.begin() + n + 1);
    z2.erase(z2.begin(), z2.begin() + n + 1);
    std::reverse(z2.begin(), z2.end());
    std::vector<int> z3(n);
    for (int i = 0; i < m; i++)
      z3[i - z2[i] + 1] = std::max(z3[i - z2[i] + 1], z2[i]);
    int ans = 0;
    int pre = -1;
    for (int i = 0; i < m; i++) {
      if (i + z1[i] - n0 + 1 > m || i + z1[i] - n0 + 1 < 0) continue;
      if (z1[i] + (i ? z2[i - 1] : 0) >= n0) {
        if (pre == i + z1[i]) continue;
        ans += z1[i] + (i ? z2[i - 1] : 0) - n0 + 1;
        pre = i + z1[i];
      }
    }
    std::cout << ans << "\n";
  }
  
}

B

朴素背包。

std 告诉我们先随机打乱,前 \(i\) 关期望获得 \(E=ik/n\) 颗星星,找一个阈值 \(B\),只更新 \([E-B, E+B]\) 内的 dp 值就行。

#include <bits/stdc++.h>

using LL = long long;

int main() {
  int t; scanf("%d", &t); while (t--) {
    int n, k; scanf("%d %d", &n, &k);
    std::vector<LL> f(k + 1, 1e17);
    f[0] = 0;
    for (int i = 0; i < n; i++) {
      std::vector<LL> g(f);
      for (int j = 1, t; j <= 4; j++) {
        scanf("%d", &t);
        for (int x = 0; x <= k - j; x++)
          g[x + j] = std::min(g[x + j], f[x] + t);
      }
      f.swap(g);
    }
    printf("%lld\n", f[k]);
  }
}

C

不是我开的。

\(\max(x, y) \cdot |x-y| = \max(x, y)^2 - \max(x, y) \min(x, y) = \max(x, y)^2 - xy\)

后者是容易的,前者用树上启发式合并,\(O(n \log^2 n)\)

队友赛时写了个线段树合并,好强啊。

D

晚点写。

E

\(S\) 为偶数,设平局概率为 \(p\),答案是 \((1-p)/2\)

否则看一下前 \((n-1)/2\) 局就平的概率,如果平了是 A 赢,否则是 \(1/2\)

如何算平局的概率?给每个字母标号,统计 \(A_i - B_i\) 有序对构成的可重集。因为可重,总方案数是 \(S!/(S/2)!\),而合法的方案数是 \(\prod c_i!/(c_i/2)!\)

能算错平局的概率,脑子已经失去了。想钦定顺序,忘了两边并不等价。

#include <bits/stdc++.h>

// 省略 modint

int main() {
  initC(1e7 + 1);
  int T; scanf("%d", &T); while (T--) {
    int n; scanf("%d", &n);
    std::vector<int> c(26, 0);
    int sum = 0;
    modint ans = 1;
    for (int i = 0, x; i < n; i++) {
      char s; scanf(" %c %d", &s, &x);
      c[s - 'a'] += x, sum += x;
    }
    int cnt = 0;
    for (int i = 0; i < n; i++) if (c[i] % 2 == 1) ++cnt;
    if (cnt > 1) { printf("%d\n", (mod + 1) / 2); continue; }
    for (int i = 0; i < 26; i++) {
      ans *= fac[c[i]] * ifac[c[i] >> 1];
    }
    ans *= ifac[sum] * fac[sum >> 1];
    // printf("%d\n", ans);
    if (cnt == 0)
      printf("%d\n", (1 - ans) / 2);
    else 
      printf("%d\n", (1 + ans) / 2);
  }
}

F

赛时在想拆贡献 dp,越想越远。

但三次方有另一个拆贡献方法:挑三个位于该集合内的数的方案数。据此 dp 即可,前缀和优化转移。

G

竞赛图三元环的经典转化是:考虑非三元环,必存在一个点出度为 \(2\)

三维偏序求每个点出度即可。

H

拆位即可。

I

假设已经确定了数字串,考虑 dp:\(f_{i, j, k}\) 表示扫完了数字串前 \(i\) 位,匹配模式串到第 \(j\) 位,且最后一位为 \(k\) 的方案数。

数位 dp 会把答案转化为:\(n |\sum|\) 次先确定前面若干位,再让后面随便填。

前半相当于数字串已经被确定,对于后半,\(f_{0/1, i, j, k}\) 表示前面是否全部是前导零,还能填 \(i\) 位,匹配模式串到第 \(j\) 位,最后一位为 \(k\) 的方案数,暴力合并。

#include <bits/stdc++.h>

const int mod = 998244353;

using v2 = std::vector<std::vector<int>>;
using v3 = std::vector<v2>;
using v4 = std::vector<v3>;

void solve() {
  std::string l, r, s;  std::cin >> l >> r >> s;
  std::reverse(r.begin(), r.end());
  r = r + '0';
  for (int i = 0; i < (int)r.length(); i++) {
    if (r[i] < '9') { ++r[i]; break; }
    r[i] = '0'; 
  }
  while (r.length() && r.back() == '0') r.pop_back();
  std::reverse(l.begin(), l.end());
  while (l.length() < r.length()) l.push_back('0');
  std::reverse(r.begin(), r.end());
  std::reverse(l.begin(), l.end());
  // std::cerr << l << " " << r << std::endl;

  int n = r.length(), m = s.length();
  v4 f(2, v3(n + 1, v2(m + 2, std::vector<int>(10, -1))));
  // 是否全是 0 T 匹配到第 p 位 子序列选出了 q 个数 最后一个数为 x
  std::function<int(int, int, int, int)> dfs = [&](int b, int p, int q, int x) {
      // std::cerr << b << " " << p << " " << q << " " << x << std::endl;
    int &ans = f[b][p][q][x];
    if (ans != -1) return ans;
    if (p == n) return ans = (q == m + 1);
    if (q == m + 1) return ans = 10ll * dfs(0, p + 1, q, x) % mod;
    ans = 0;
    for (int y = 0; y < 10; y++) {
      (ans += dfs(b && !y, p + 1, q, x)) %= mod;
      if (b && !y) continue;
      if (q && s[q - 1] == '<' && x >= y) continue;
      if (q && s[q - 1] == '>' && x <= y) continue;
      (ans += dfs(0, p + 1, q + 1, y)) %= mod;
    }
    return ans;
  };
  auto solve = [&](std::string x) {
    int n = x.length();
    auto p = x;
    // 已经匹配模式串的前 i 位,最后一位为 j
    v2 f(n + 1, std::vector<int>(10, 0));
    // 在 f 后面放一个 y
    auto nxt = [&](int y) {
      auto g = f;
      ++g[0][y];
      for (int i = 0; i < m; i++) {
        for (int x = 0; x < 10; x++) {
          if (s[i] == '<' && x >= y) continue;
          if (s[i] == '>' && x <= y) continue;
          (g[i + 1][y] += f[i][x]) %= mod;
        }
      }
      return g;
    };
    int ans = 0;
    bool c = 1;
    int i = 0; while (x[i] == '0') ++i;
    for (; i < n; i++) {
      int t = x[i] - '0';
      for (int y = 0; y < t; y++) {
        (ans += dfs(c && !y, i + 1, 0, 0)) %= mod;
        if (c && !y) continue;
        auto g = nxt(y);
        for (int j = 0; j <= m; j++) 
          for (int x = 0; x < 10; x++) if (g[j][x]) {
            (ans += 1ll * g[j][x] * dfs(0, i + 1, j + 1, x) % mod) %= mod;
          }
      }
      c &= (x[i] == '0');
      f = nxt(t);
    }
    return ans;
  };
  int R = solve(r);
  f.assign(2, v3(n + 1, v2(m + 2, std::vector<int>(10, -1))));
  // std::cout << R << " " << solve(l) << "\n";
  std::cout << (R - solve(l) + mod) % mod << "\n";
}

int main() {
  std::ios::sync_with_stdio(0);
  int T; std::cin >> T; while (T--) {
    solve();
  }
}

J

从大到小枚举众数。开一个优先队列,

posted @ 2024-07-20 17:27  purplevine  阅读(154)  评论(2编辑  收藏  举报