题目:2024.10.21 题目

串串串

有两个长度为 字符串 ,给定 次询问,每次询问给出 ,其中 ,令 ,求 的位置数量对 取模的结果。


手推可以发现,交换区间内的任意两个位置 ,答案的奇偶性都是不变的。具体而言:如果位置 与位置 相等,那么交换了就跟没交换一样;如果两个位置上的数不一样,那么答案可能会根据实际情况加 或减 ,答案的奇偶性没有变化。

因此我们只需要使用两个前缀和数组记录 任意区间的 的数量,如果询问的两个区间 的数量相等就输出 ,否则输出

// 出题人,柠檬熟了
 
#include <fstream>
 
using namespace std;
using LL = long long;
 
const LL kMaxN = 2e5 + 5;
 
ifstream cin("string.in");
ofstream cout("string.out");
 
LL p1[kMaxN], p2[kMaxN], n, m, q;
char s[kMaxN], t[kMaxN];
 
int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> m >> (s + 1) >> (t + 1) >> q;
  for (LL i = 1; i <= n; i++) {
    p1[i] = p1[i - 1] + (s[i] == '1');
  }
  for (LL i = 1; i <= m; i++) {
    p2[i] = p2[i - 1] + (t[i] == '1');
  }
  for (LL l1, r1, l2, r2; q; q--) {
    cin >> l1 >> r1 >> l2 >> r2;
    cout << (LL)((p1[r1] - p1[l1 - 1]) % 2 != (p2[r2] - p2[l2 - 1]) % 2) << '\n';
  }
  return 0;
}

方格计数

给定 ,有 的网格,现在要在网格上找 个不同的结点,使得这些点在同一条直线上,并且在这条直线上相邻点的距离不小于 ,求方案数对 取模的值。


枚举 个点分别在哪个位置上肯定不行,因此我们不如枚举 个点所在的直线上。很容易想到枚举直线的斜率,然后再枚举直线上的两个端点,在算出答案。但是这样子代码过于复杂,而且枚举斜率很容易重复。

小学老师交过我们一件事:「两点确定一条直线」。这也就意味着我们根本就没有必要枚举直线的斜率,我们可以直接枚举直线两边的端点,即可确定这一条直线/线段。根据初中老师所讲的知识,假设两点之间 坐标差值为 坐标差值为 ,则这两点之间连接的线段将会在网格上经过 个点。

此时我们已经选择了两个端点,因此还需要选择 个直线上的端点。由于题目中指出相邻两点距离至少为 ,因此我们将直线经过的点编号,令任意相邻两个点编号差不能小于 ,这时学过组合数的都应该知道了,答案为

因此我们预处理杨辉三角用来计算组合数,或者预处理阶乘使用费马小定理计算组合数(因为在这题当中模数为质数)。此时时间复杂度为 ,可以通过 分。

接下来考虑如何得到满分,其实非常简单。不难发现,在枚举的过程中,有很多直线它们斜率相等或互为倒数,这时我们就可以把这些直线看成相等的,同一枚举并计算。假设有一个矩形框,这条直线就是矩形的对角线,矩形的宽为 ,高为 ,则为同样的矩形有 个,乘上即可。

最后就是细节的处理了。对于 的矩形,我们需要将答案翻倍,很明显是因为对角线有 条。这样我们就成功地将时间复杂度优化到了 ,可以通过 分。

#include <fstream>
#include <cmath>
 
using namespace std;
using LL = long long;
 
const LL kMaxN = 3005, kMod = 1e9 + 7;
 
ifstream cin("count.in");
ofstream cout("count.out");
 
LL c[kMaxN][kMaxN], f[kMaxN], t, n, w, h, d, ans;
 
void pre() {
  cin.tie(0)->sync_with_stdio(0);
  c[0][0] = 1;  // 没有初始化
  for (LL i = 1; i <= 3000; i++) {
    c[i][0] = 1;
    for (LL j = 1; j <= i; j++) {
      c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % kMod;
    }
  }
  // f[0] = 1;
  // for (int i = 1; i <= 3000; i++) {
  //   f[i] = f[i - 1] * i % kMod;
  // }
}
 
LL gcd(LL a, LL b) { return !b ? a : gcd(b, a % b); }
 
double dis(LL x, LL y) { return sqrt(x * x + y * y); }
 
// LL pow(LL a, LL b) {
//   LL res = 1;
//   for (; b; b /= 2) {
//     b & 1 && (res = res % a % kMod);
//     a = a * a % kMod;
//   }
//   return res;
// }
//
// LL inv(LL x) { return pow(x, kMod - 2); }
 
LL fuck(LL i, LL j) {
  LL g = gcd(i, j), l = (LL)ceil(1.0 * d / dis(i / g, j / g)) /* 差 */;
  if ((n - 1) * l > g) { return 0; }
  return c[g - (n - 3) * (l - 1) - 2 * l + 1][n - 3 + 1] *  // 组合数
         (i && j ? 2 : 1) % kMod *                          // 一正一反
         (w - i + 1) % kMod *                               // 横方向
         (h - j + 1) % kMod;                                // 竖方向
}
 
int main() {
  pre();
  for (cin >> t; t; t--) {
    cin >> n >> w >> h >> d;
    // BYD 的,没有特判调了半小时,我 /* */
    if (n == 1) {
      cout << (w + 1) * (h + 1) << '\n';
      continue;
    }
    ans = 0;
    for (LL i = 0; i <= w; i++) {
      for (LL j = 0; j <= h; j++) {
        if (!i && !j) {
          continue;
        }
        ans = (ans + fuck(i, j)) % kMod;
      }
    }
    cout << ans << '\n';
  }
  return 0;
}

树数树

个结点的树,现在求一个最长的序列 ,满足:

  • 的祖先或 的祖先;

组数据。


不会写,只判断了树为链或菊花的情况,获得 分,赛后发现没有特判完全二叉树的形状,本来能拿 分。本体正解合并堆(左偏树),虽然好渴鹅学过,但是没有发现!你干嘛——哈哈哎哟~

序列

对于数 ,它的数位和为 的字段被称作 se 序列,如果它的被一个数位都至少在一个 se 序列中,则称 是 II 数。给定 ,令 (满足 ),随机生成一个 范围内的数,每一位上生成数字 的概率为 ,求这个数为 II 数的概率,答案对 取模。


不会写,对于 的情况特判并输出 ,在 widwolf 上面理所应当地获得了 分的好成绩,但是在 lemon 上面没有分,老师说题面改了(具体的可以查看 2024.10.21 星期一),不知道最后在 linux 上面能够获得多少分,反正都一样。

总结

  • 排名
  • 比赛分数
  • 情况:相比 2024.10.20 模拟赛,还可以;
  • 问题:突然不会左偏树的应用了。