P6393. 沙漠绿洲
题解
首先,使最小值最大化很自然想到二分答案,然后我们考虑如何判定。
当 \(c_i \leq 5\) 时,我们很自然的可以想到一个状压 dp,我们先将所有物品按照 \(w_i\) 排序,记 \(f[i][S]\) 为选择到第 \(i\) 个物品,选了 \(S\) 中颜色,\(S\)
为一个 \(5\) 位的二进制,那么这样的复杂度就是 \(O(2^5 \times n \log n)\) 的,可以通过 \(Subtask 2\) 和 \(Subtask 5\)。
当 \(c_i \leq n\) 时,受 CSP 2022 T1 的随机化做法的启发,我们可以想到把 \(c_i\) 映射到 \(1 \sim 5\) 里,然后我们多随机映射几次,正确的可能性就很高了。
于是我们就可以沿用 \(c_i \leq 5\) 时的做法,经过测试,这种映射方法差不多随机 \(200\) 次后的正确率是可以接受的,不过该做法的复杂度为 \(O(2^5 \times 200 \times n \log n)\),且由于时限被开小到了 \(1S\),无法通过全部的数据。
然后我们注意到 \(f[i][S]\) 数组存储的仅为该种方法是否能达成,且转移时要一位一位转移,十分低效,于是我们就可以考虑进行压位。
注意到 \(5\) 中颜色的选择情况一共只有 \(32\) 种,我们可以用一个 unsigned int 存储下来,那么现在我们转移时就可以直接通过对状态进行位移来转移,十分高效,同时为了提高正确率,我们可以改用 unsigned long long 来进行压位,将 \(c_i\) 映射进 \(1 \sim 6\) 中,经测试,使用 unsigned long long 进行压位只要差不多 \(70\) 次随机即可通过数据,复杂度为 \(O(70 \times n \log n + 2^{6 - m} \log n)\),可以通过本题。
代码
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <algorithm>
using namespace std;
const int N = 1e4 + 10;
const int T = 70;
int n, m, c[N], v[N], maxc, passed[65], tot;
pair<int, int> a[N];
unsigned long long f[N], except[6];
bool check(int k) {
for (int t = 1; t <= T; t ++) {
memset(v, -1, sizeof(v));
for (int i = 1; i <= n; i ++) c[i] = v[a[i].second] != -1 ? v[a[i].second] : v[a[i].second] = rand() % 6;
f[0] = 1;
for (int i = 1, j = 0; i <= n; i ++) {
while (a[i].first - a[j + 1].first >= k) ++ j;
f[i] = f[i - 1];
f[i] |= (f[j] & except[c[i]]) << (1ll << c[i]);
}
for (int i = 1; i <= tot; i ++) if (f[n] >> passed[i] & 1) return true;
}
return false;
}
void solve() {
maxc = 0;
scanf("%d%d%*d", &n, &m);
tot = 0;
for (int S = 0; S < 1 << 6; S ++) if (__builtin_popcount(S) >= m) passed[++ tot] = S;
for (int i = 1; i <= n; i ++) scanf("%d%d", &a[i].second, &a[i].first);
sort(a + 1, a + 1 + n);
int l = 0, r = 1e6, ans = -1;
while (l <= r) {
int mid = (l + r) >> 1;
if (check(mid)) ans = mid, l = mid + 1;
else r = mid - 1;
}
printf("%d\n", ans);
}
int main() {
srand(6797956);
for (int i = 0; i < 6; i ++) for (int S = 0; S < 1 << 6; S ++) if (!(S >> i & 1)) except[i] |= 1ll << S;
int T; scanf("%d", &T);
while (T --) solve();
}
浙公网安备 33010602011771号