题目:2024.10.24 题目

长方体

个长方体,第 个对角线的两个顶点为 。求至少被 个长方体所覆盖的整点数量。


显然若答案求的是被正好 个长方体覆盖的整点数量很好算,对于下标为 的我们取最大值,对于下标为 的我们取最小值,最后最小值前去最大值,三个维度乘起来即可。

想到这一步,转换到 个长方体的求法就很简单了。我们可以枚举 ,假设 不算入答案,那么对 的长方体我们就可以使用 个长方形的求法,时间复杂度 。不难想到答案只需要各个维度的最大值与最小值,因此可以做一下前缀最大值、前缀最小值、后缀最大值、后缀最小值,这样子时间复杂度就可以有效地降到 。上面所述的方法有很大的问题,就是对于有些点,可能去掉多个不同的长方形仍然可以算在内,因此我们考虑反向容斥。

对于这种点,它们只可能是 个长方形都共有的部分。因此我们提前将这一部分的体积算出来,假设有 个长方形被去掉后 个长方形仍然有共同的部分,那么答案就减去 ,其中 是这一部分的体积。问什么要减一呢?因为凡事都不能做得太绝对,毕竟「做人留一线,日后好相见。」

#include <fstream>
 
using namespace std;
using LL = long long;
 
const LL kMaxN = 1e5 + 5;
 
ifstream cin("cube.in");
ofstream cout("cube.out");
 
struct Cube {
  LL x0, y0, z0, x1, y1, z1;
} a[kMaxN];
 
LL premax[kMaxN][3], premin[kMaxN][3], sufmax[kMaxN][3], sufmin[kMaxN][3], n, ans;
 
int main() {
  cin >> n;
  for (LL i = 1; i <= n; i++) {
    cin >> a[i].x0 >> a[i].y0 >> a[i].z0 >> a[i].x1 >> a[i].y1 >> a[i].z1;
  }
  premax[0][0] = premax[0][1] = premax[0][2] = -1e18;
  premin[0][0] = premin[0][1] = premin[0][2] = 1e18;
  for (LL i = 1; i <= n; i++) {
    premax[i][0] = max(premax[i - 1][0], a[i].x0);
    premax[i][1] = max(premax[i - 1][1], a[i].y0);
    premax[i][2] = max(premax[i - 1][2], a[i].z0);
 
    premin[i][0] = min(premin[i - 1][0], a[i].x1);
    premin[i][1] = min(premin[i - 1][1], a[i].y1);
    premin[i][2] = min(premin[i - 1][2], a[i].z1);
  }
  sufmax[n + 1][0] = sufmax[n + 1][1] = sufmax[n + 1][2] = -1e18;
  sufmin[n + 1][0] = sufmin[n + 1][1] = sufmin[n + 1][2] = 1e18;
  for (LL i = n; i >= 1; i--) {
    sufmax[i][0] = max(sufmax[i + 1][0], a[i].x0);
    sufmax[i][1] = max(sufmax[i + 1][1], a[i].y0);
    sufmax[i][2] = max(sufmax[i + 1][2], a[i].z0);
 
    sufmin[i][0] = min(sufmin[i + 1][0], a[i].x1);
    sufmin[i][1] = min(sufmin[i + 1][1], a[i].y1);
    sufmin[i][2] = min(sufmin[i + 1][2], a[i].z1);
  }
  for (LL i = 1; i <= n; i++) {
    LL a1 = max(premax[i - 1][0], sufmax[i + 1][0]), a2 = min(premin[i - 1][0], sufmin[i + 1][0]);
    LL b1 = max(premax[i - 1][1], sufmax[i + 1][1]), b2 = min(premin[i - 1][1], sufmin[i + 1][1]);
    LL c1 = max(premax[i - 1][2], sufmax[i + 1][2]), c2 = min(premin[i - 1][2], sufmin[i + 1][2]);
    if (a2 == 1e18 || b2 == 1e18 || c2 == 1e18) {
      continue;
    }
    if (a1 <= a2 && b1 <= b2 && c1 <= c2) {
      ans += (a2 - a1 + 1) * (b2 - b1 + 1) * (c2 - c1 + 1);
      ans -= max(0LL, premin[n][0] - premax[n][0] + 1) *
             max(0LL, premin[n][1] - premax[n][1] + 1) *
             max(0LL, premin[n][2] - premax[n][2] + 1);
    }
  }
  cout << ans + max(0LL, premin[n][0] - premax[n][0] + 1) *
                max(0LL, premin[n][1] - premax[n][1] + 1) *
                max(0LL, premin[n][2] - premax[n][2] + 1) << '\n';
  return 0;
}

三角形

给定长度为 的序列 ,第 个数为 。进行 次询问,每次询问的格式为 ,求区间内是否能够选出三个不同的下标 使得三条 的线段可以组成三角形。


不会写,采用暴力。首先我们可以对区间 内的所有元素都进行一个 的枚举,然后判断是否满足条件。这种枚举是最暴力最纯洁得了,总时间复杂度

不难想到优化,假设 是最小的那两个,并且 ,只枚举 肯定是选择满足 的,且 尽量小,这样子对于一个区间我们只需要花费 ,总时间复杂度

考虑继续优化。此时忽然想起小学数学老师告诉过我们的,对于一个数列,想要选择三个数的长度的线段组成三角形,只需要选择大小相邻的就行了。因此我们可以对 内的元素排序,然后三个三个判断,这样子的总时间复杂度就降到了 。可以获得 分。

正解:考虑最坏的情况,无法组成三角形。由于 ,因此最坏的情况是构造类斐波那契数列的 的序列。由于斐波那契数列在 的范围之内只有不到 个数,因此大于 的情况就可以直接输出有解,否则采用暴力判断。

区间

给定长度为 的序列 ,第 个数为 ,并给定 次操作,每次操作给出 。若 ,则翻转区间 内的元素;如果 ,则获取 。对于所有的操作 ,求出答案的异或和。


采用暴力。对于操作一,我们直接使用两个指针 通过交换来实现区间翻转;对于第二个操作直接输出 即可。该算法的时间复杂度为 ,在对应的数据范围中理论上只能获得 分,但是由于数据过水,此做法实际上可以获得 分。

个结点,第 个结点的点权为 。若 是按位与),则 有一条边。初始时集合 ,进行 次操作,每次加入一个结点到 。加入结点 有两种方式:

  • 直接加入结点 ,代价为
  • 选择另一个结点 之间有一条边,代价为

求能够获得的最大代价。


赛时一眼想到了最大生成树,但是由于大样例挂掉红温便没有继续调试,实际上我的代码改几行就可以通过。

我提交的代码是采用状态压缩动态规划的版本。令 表示为当前状态为二进制 (第 位为 代表第 个结点已经加入了集合,反之反之),那么我们可以枚举新加入哪一个结点,必须是 里面没有的;再枚举和哪一个连边,找出最大值,然后转移即可。

时间复杂度为 ,侥幸获得 分。

总结

  • 排名
  • 比赛分数
  • 情况:相比 2024.10.23 模拟赛,没区别;
  • 问题:调不出题目就红温。