题目:2024.10.14 题目

通配符

个不同的字符串,第 个字符串为 。又有长度为 的含有 的字符串 ,通配符 可以在必要时变为任意字符串包括空串。对于 的任意连续子串 ,若 能匹配任意 则称 是好的。求好的 的数量。


对于 的点,我们考虑枚举子串,然后判断子串是否能满足条件。设 表示为子串的前 个字符能否匹配任意 的前 个字符。这是一个很基础的序列 dp,对于单个字符串的匹配时间复杂度为 ,总时间复杂度为

加下来我们考虑如何将枚举子串判断转换为对于 有什么类型的子串能够匹配。令 表示为有多少个以 结尾的子串能够匹配 的前 位。此时可能会有两种情况:,那么可以匹配任意多位的 ,类似完全背包,让 ;否则我们枚举可能匹配到的 ,再判断 是否等于 ,如果是就代表可以匹配多一位 ,否则不能匹配,为 。注意,这里的第一个维度是表示「以 结尾」,这能够帮助我们在最后统计的时候统计所有的子串。此时, 里面装的就是以 结尾、匹配了 整个字符串的子串数量()。

但是我们仍然不能达到以任意位置结尾(任意子串),能够匹配 的数量。因此我们加一个额外转移:,这样哪怕没有匹配这一位也会继续下去,就能够达成我们的目的。此外,为了防止爆空间,我们可以对 采取滚动数组或压数组的方式避免。时间复杂度

#include <fstream>
#include <string>
 
using namespace std;
using LL = long long;
 
const LL kMaxN = 5e4 + 5, kMod = 998244853;
 
ifstream cin("char.in");
ofstream cout("char.out");
 
LL dp[kMaxN], n, m, T, ans;
string s;
 
int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> T >> s;
  s = ' ' + s;
  for (string t; T; T--) {
    cin >> t;
    m = t.size();
    t = ' ' + t;
    fill(dp, dp + m + 1, 0);
    for (LL i = 1; i <= n; i++) {
      dp[0]++;
      if (s[i] != '*') {
        for (LL j = m; j >= 1; j--) {
          dp[j] = s[i] == t[j] ? dp[j - 1] : 0;
        }
        dp[0] = 0;
      } else {
        for (LL j = 1; j <= m; j++) {
          dp[j] = (dp[j] + dp[j - 1]) % kMod;
        }
      }
      ans = (ans + dp[m]) % kMod;
    }
  }
  cout << ans << '\n';
  return 0;
}
 

个点的树,第 个点上染了 的颜色。设 ,则树的价值为 ,其中 表示 的简单路径上的边权最大值。可以修改最多一个点使它的颜色反转,求最大价值。


利用惊人的注意力注意到 (坏笑),可以使用 量级的算法。不难想到,我们可以枚举需要改变颜色的位置 ,将 反转,然后在反转的基础上求出树的价值,这样压力就落到了 求树的价值上了。

由于本题当中距离为取最大值而非累加的方式,因此我们可以以权值为单位进行处理。画图发现,对于一条边 ,有两个连通块分别为 连接且权值不大于 的,那么此时就可以将答案累加上 两个连通块颜色不同的组合数量乘上 ,因此我们考虑如何合并满足要求的连通块。

合并连通块很容易想到并查集。我们按照权值 对每一条边从小到大进行排序,此时对于边 的连通块都是满足条件的(连通块内所有边边权均不大于 ),我们直接进行累加,然后合并 所在的联通块。

使用额外数组 记录 所在连通块内黑白结点的数量,并在并查集合并时累加,即可完成需要的任务。时间复杂度

后记:有巨佬整出了 解法,就是 预处理任意两个点之间的距离,然后再 枚举更改颜色的结点并求出答案,比 优秀一点。

#include <fstream>
#include <algorithm>
 
using namespace std;
using LL = long long;
 
const LL kMaxN = 3e3 + 5;
 
ifstream cin("tree.in");
ofstream cout("tree.out");
 
struct Edge {
  LL u, v, w;
} e[kMaxN];
 
LL c[kMaxN], f[kMaxN], s[kMaxN][2], n, ans;
 
LL findMotherFucker(LL x) {
  return !f[x] ? x : f[x] = findMotherFucker(f[x]);
}
 
void getMarried(LL u, LL v) {
  f[u] = v;
  s[v][0] += s[u][0];
  s[v][1] += s[u][1];
}
 
LL fuck() {
  fill(f + 1, f + n + 1, 0);
  for (LL i = 1; i <= n; i++) {
    s[i][0] = c[i] == 0;
    s[i][1] = c[i] == 1;
  }
  LL res = 0;
  for (LL i = 1; i < n; i++) {
    LL x = findMotherFucker(e[i].u);
    LL y = findMotherFucker(e[i].v);
    res += s[x][0] * s[y][1] * e[i].w;
    res += s[x][1] * s[y][0] * e[i].w;
    getMarried(x, y);
  }
  return res;
}
 
int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n;
  for (LL i = 1; i <= n; i++) {
    cin >> c[i];
  }
  for (LL i = 1; i < n; i++) {
    cin >> e[i].u >> e[i].v >> e[i].w;
  }
  sort(e + 1, e + n, [](auto a, auto b) {
    return a.w < b.w;
  });
  ans = max(ans, fuck());
  for (LL i = 1; i <= n; i++) {
    c[i] ^= 1;
    ans = max(ans, fuck());
    c[i] ^= 1;
  }
  cout << ans << '\n';
  return 0;
}
 

木棍

有一根长度为 的木棍,可以变出另外三根木棍 满足 。如果这 根木棍能够组成三角形,则称这次变换为成功的变换。求可能的成功的变换的数量。


采用暴力。枚举 并判断可以达到 。然后开始无用优化:不难发现拼凑三角形只会选择长度相邻的三根小木棒,因此我们可以只枚举 然后通过三角形的定义算出 的范围。时间复杂度 ,和 一样都是 分。

群星

有一游戏分为 个世纪,第 个世纪会发生 场最后战争。游戏里有 个文明,第 个文明初始排名为 。每场战争会在 对文明中随机选取一对,交换排名。对于每个世纪,你需要求出在任意世纪选择的文明,在世纪结束后最终期望的排名的最小值。保留五位小数输出。


没有看懂题目,文件都没有创建, 分。赛后发现全部输出 和第 行输出 均能够获得 分,出题人你没马 🐴。

总结

  • 排名
  • 比赛分数
  • 情况:相比 2024.10.13 模拟赛,好;
  • 问题:找规律、数学推导水平较弱。