题目
[CodeForces 743E] Vladik and cards
分析
首先看到“任意两种数的数量相差不超过 ”,并且只有 种数,考虑状压每种数是 , 还是 ,然后发现是 还是 不重要,转移的时候看一下哪种选法更优即可,因为某种数数量是否多 是无后效性的。但是 需要枚举,然后观察发现 显然可以二分。于是有了一个初步发想法是二分 并状压 DP 检验。 表示前 个数,选的数的种类的集合为 的最长子序列长度。预处理每种数在原序列中的位置,转移的时候往里面塞 或 个当前数即可。
据说可以二分 + 暴力
水过去。
代码
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>
const int MAXN = 1000;
const int MAXA = 8;
int N;
int A[MAXN + 5];
std::vector<int> Pos[MAXA + 5], Tmp[MAXA + 5];
int Dp[MAXN + 5][(1 << MAXA) + 5];
int Check(int num) {
for (int i = 1; i <= MAXA; i++)
Tmp[i] = Pos[i];
memset(Dp, -0x3f, sizeof Dp);
Dp[0][0] = 0;
for (int i = 0; i < N; i++) {
int lim = (1 << MAXA) - 1;
for (int j = 0; j <= lim; j++) {
if (Dp[i][j] >= 0) {
for (int k = 0; k < MAXA; k++)
if (!(j & (1 << k))) {
if (Tmp[k + 1].size() > num)
Dp[Tmp[k + 1][num]][j | (1 << k)] = std::max(Dp[Tmp[k + 1][num]][j | (1 << k)], Dp[i][j] + 1);
if (Tmp[k + 1].size() >= num)
Dp[Tmp[k + 1][num - 1]][j | (1 << k)] = std::max(Dp[Tmp[k + 1][num - 1]][j | (1 << k)], Dp[i][j]);
}
// printf("%d %d %d\n", i, j, Dp[i][j]);
}
}
if (A[i])
Tmp[A[i]].erase(Tmp[A[i]].begin());
}
int res = -1;
for (int i = 1; i <= N; i++)
res = std::max(res, Dp[i][(1 << MAXA) - 1]);
if (res == -1)
return -1;
return num * 8 + res;
}
int main() {
scanf("%d", &N);
for (int i = 1; i <= N; i++) {
scanf("%d", &A[i]);
Pos[A[i]].push_back(i);
}
for (int i = 1; i <= MAXA; i++)
if (!Pos[i].size()) {
int Ans = 0;
for (int j = 1; j <= MAXA; j++)
if (Pos[j].size())
Ans++;
return printf("%d", Ans), 0;
}
int lft = 1, rgt = N / 8 + 1;
while (lft + 1 < rgt) {
int mid = (lft + rgt) >> 1;
if (~Check(mid))
lft = mid;
else
rgt = mid;
}
printf("%d", Check(lft));
return 0;
}