伸展

二分 数学 前缀和 后缀和

首先答案是可以二分的,当 达到比所有的相邻 的差值都要大的时候那么 绝对是满足条件的,而如果一个 满足条件那么 也是满足的,因此可以二分答案 值。

考虑如何判断 是否满足条件。令 表示为当前线段的左端点,那么很显然最开始时 是最优的。接下来枚举 ,判断能否伸到当前的点,即判断 ,如果不满足可以直接返回不满足条件;否则令 ,也就是往右铺 的长度,但是要保证下一个点仍然可以被覆盖。

时间复杂度 ,可以通过 分。由于有多组询问,可以尝试整体二分,但是由于我整体二分的 check 没有写好导致复杂度和正常二分是一样的。

因此我们需要找出规律。把上一个 的值带入进去,拆开 ,就可以得到 个不等式:

对于第 个不等式,它描述了从 开始能否走到 。多个数的 值大于等于一个值,那么这每一个数都要大于等于那个数,以第三个式子举例

准确地来说,对于从 开始走的情况,如果最终要走到 ,它的必要条件是对于 ,有 ,由于 是整数解方程得到 。因此 最终的取值是

当所有必要条件都满足( 能走通, 能走通,……, 能走通)时, 才是真正可以走通的,因此 要是所有 要求的 值的最小值中的最大值。也就是最终 的答案等于

做两遍后、前缀 即可,也可以一遍搞完。时间复杂度

#include <iostream>
#include <cmath>
 
using namespace std;
using LL = long long;
 
const LL kMaxN = 5005;
 
LL a[kMaxN], ans[kMaxN][kMaxN], n, q;
 
int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> q;
  for (LL i = 1; i <= n; i++) {
    cin >> a[i];
  }
  for (LL r = n; r >= 1; r--) {
    for (LL l = r - 1; l >= 1; l--) {
      ans[l][r] = max(ans[l + 1][r], (a[r] - a[l] + r - l) / (r - l + 1));
    }
  }
  for (LL l = 1; l <= n; l++) {
    for (LL r = l + 1; r <= n; r++) {
      ans[l][r] = max(ans[l][r], ans[l][r - 1]);
    }
  }
  for (LL l, r; q; q--) {
    cin >> l >> r;
    cout << ans[l][r] << '\n';
  }
  return 0;
}

内鬼

我写的暴力。首先枚举删除的两个点,然后枚举相交的弦并累加答案。唯一有难点的是如何判断两个弦是否相交,最简单的道理是拿出其中一个弦 ,不满足就交换),另外一个弦 其中一个属于 ,另外一个不属于 。时间复杂度 。成功获得 分。

零一

我还是写的暴力。暴力枚举左端点然后往右找右端点,加上奇妙小剪枝,时间复杂度 但是可以通过 分。

反转

我分类写的暴力。对于 的数据,猜测答案不会太大所以进行迭代加深搜索;对于 但是答案不超过 的,先特判答案为 然后在枚举操作的点是什么。用并查集判断连通性。成功获得 分。

总结

总分:

这场比赛相对于之前还是比较良好的,虽然在第一题坐牢了三个小时但是最终还是写出来了,后面的暴力也很稳,暴力本身没有挂掉,我也没有漏掉我不会会写的子测试点。只是水平还是有限了,第二题不会容斥,无法追求更高的分数,等会讲题的时候再看看。