Problem
给你 \(m\) 个 \(1\) 到 \(n\) 之间的整数,你要找到若干个大小为固定的 \(k\) 的闭区间,使得所有这些数都在你找到的某个区间内。你需要最小化这些区间的并集的大小,并输出此大小。本题里区间或区间并集的大小,被定义为这个区间或区间并集里整数的个数。\(1 \le k \le n \le 10^9, 1 \le m \le 3 \times 10^5\)。
Solution
若有两个数为 \(9, 14\),则要用两个区间 \([9, 12]\) 和 \([11, 14]\) 来覆盖,此时这两个区间是有交集的;若有两个数为 \(1, 9\),则要用两个区间 \([1, 4]\) 和 \([6, 9]\) 来覆盖,此时这两个区间没有交集。若干个有交集的区间构成的长段和没有交集的单个段是两种方案,可以发现覆盖数组中的数只有这两种方案。
根据题意,覆盖长度为 \(k\),则要划分两个段只需要判断上一段的结尾与这一段的开头差值是否超过 \(k\) 即可。若差值超过 \(k\),则需要使用这两个数之间的所有数覆盖;若差值不超过 \(k\),直接用 \(k\) 个数覆盖。使用 dp 并两层循环枚举当前数和上一段的结尾,这一段即为 \([j+1, i]\),差值即为 \(a_i - a_{j+1} + 1\)。时间复杂度为 \(O(m^2)\),可以得到 70pts。
70pts
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 3e5 + 7;
int a[N], f[N];
int main() {freopen("cpu.in", "r", stdin);freopen("cpu.out", "w", stdout);int n, k, m;scanf("%d%d%d", &n, &k, &m);for (int i = 1; i <= m; ++i)scanf("%d", a + i);sort(a+1, a+m+1);memset(f, 0x3F, sizeof f);f[0] = 0;for (int i = 1; i <= m; ++i)for (int j = 0; j < i; ++j) {int d = a[i]-a[j+1]+1;f[i] = min(f[i], f[j] + ((d>k) ? d : k));}printf("%d", f[m]);return 0;
}