JOI 2025 Final T4 Sol
比较神的题。
你把最小覆盖刻画成最长下降就寄了。因为这样没有啥比较好的性质来贪心。
反而我们可以考虑,对于选定的一些数应该怎么做。我们从左往右每次加入一个数,如果存在一个小于等于它且最大的数,就将这个数贪心替换,否则新开一个位置。
发现这样你维护的集合里面不太会有重复的数。所以状态只有
于是相当于我们可以设计出朴素的
事实上,对于每个
转移有自环。我们发现事实上需要先处理自环,这样才能保证之后的转移是优的。那自环的转移在干什么事情呢:找到第一个
暴力预处理是
- 每个位置往后第一个某个值
j 的位置。 - 每个位置往后第一个相邻二元组形如
(a_i,j) 的位置。
那么我们就可以
void fake_main() {
n = read();
rep(i, 1, n) a[i] = read(), V = max(V, a[i]), --a[i];
rep(S, 0, (1 << V) - 1) rep(i, 0, V - 1) {
int msk = S & ((1 << i + 1) - 1);
if (!msk) trans[S][i] = S | (1 << i);
else trans[S][i] = (S ^ (1 << __lg(msk))) | (1 << i);
}
rep(i, 0, V - 1) nxt1[n + 1][i] = nxt2[n + 1][i] = n + 1;
per(i, n, 0) {
rep(j, 0, V - 1) nxt1[i][j] = nxt1[i + 1][j];
if (i) nxt1[i][a[i]] = i;
}
per(i, n, 1) {
rep(j, 0, V - 1) nxt2[i][j] = nxt2[nxt1[i + 1][a[i]]][j];
if (i < n) nxt2[i][a[i + 1]] = i;
}
rep(S, 0, (1 << V) - 1) {
int tmp = n + 1;
rep(i, 0, V - 1) {
if (S >> i & 1) continue;
rep(j, 0, V - 1) {
if (S >> j & 1) continue;
tmp = min(tmp, nxt2[nxt1[f[S]][i]][j]);
}
}
f[S] = max(f[S], tmp - 1);
if (f[S] + 1 <= n) f[trans[S][a[f[S] + 1]]] = max(f[trans[S][a[f[S] + 1]]], f[S] + 1);
if (f[S] + 2 <= n) f[trans[S][a[f[S] + 2]]] = max(f[trans[S][a[f[S] + 2]]], f[S] + 2);
}
rep(S, 0, (1 << V) - 1) if (f[S] >= n - 1) ans = min(ans, (int) __builtin_popcount(S));
write(ans);
}