最小点覆盖问题
E. Algebra Flash
做这道题的时候新学的算法, 叫做最小点覆盖.
令 \(c_i\) 为在 \(i\) 位置的颜色
首先了解题意, 由于我们只能跨 \(1\) ~ \(2\)步, 故此时如果有 \(c_i = c_{i+1}\), 则 \(c_i\) 这个颜色是必选的, 若两者不相等, 也必须从两者里面选择出一个来, 那么我们可以给相邻的两点连上一条边, 意味着两个点必须有一个点被选择, 这样就转化成: 第 \(1\) 个点和第 \(n\) 个点必选, 其余相邻的点必选一个, 那么就可以转化成最小点覆盖问题, 我们可以把 \(m\) 个点对半分, 然后枚举出前 \(\frac{m}{2}\) 个点的状态, 然后再和剩下的点进行合并.
- 在枚举过程中, 会出现状态某两个点没有选择, 而我们必须要在这两个点中选择一个点, 所以我们此时的情况是无解的, 直接设置成无穷大即可
- 需要倒着枚举状态, 因为即使出现无解的情况, 我们也要对其进行更新, 也就是符合情况的最小的超集, 例如存在两个点没有选择, 而这两点必须选择一点, 那么我们首先要把这个状态设置成无穷大, 然后去枚举所有没有选择的点, 对他的超集, 也就是符合条件的进行更新, 这样做是为了在合并的时候, 只要后半段有解, 前半段即使无解, 由于我们枚举了超集, 再加上某个点使得这个状态最小, 从而合并
- 时间复杂度\(O(2^{\frac{m}{2}} * (\frac{m}{2})^2)\)
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int INF = 1e18, mod = 1e9 + 7;
signed main()
{
std::ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int n, m; cin >> n >> m;
vector<int> c(n + 1), cost(m + 1);
for(int i = 1; i <= n; i++) cin >> c[i];
for(int i = 1; i <= m; i++) cin >> cost[i];
vector<vector<bool>> a(m + 1, vector<bool> (m + 1));
for(int i = 1; i <= n; i++){
if(i > 1) a[c[i]][c[i - 1]] = true;
if(i < n) a[c[i]][c[i + 1]] = true;
}
a[c[1]][c[1]] = a[c[n]][c[n]] = true;
int z1 = m / 2, z2 = m - z1;
vector<int> dp(1LL << (z1 + 1));
for(int i = (1LL << z1) - 1; i >= 0; i--){ // O(2 ^ (m / 2) * (m / 2) * (m / 2))
bool ok = true;
for(int j = 1; j <= z1; j++){
if((i >> (j - 1) & 1) == 0){
for(int k = 1; k <= z1; k++){
if((i >> (k - 1) & 1) == 0 && a[j][k]){
ok = false;
}
}
}
}
if(ok){
for(int j = 1; j <= z1; j++){
if((i >> (j - 1) & 1)){
dp[i] += cost[j];
}
}
} else{
dp[i] = INF;
for(int j = 1; j <= z1; j++){
if((i >> (j - 1) & 1) == 0){
dp[i] = min(dp[i], dp[i ^ (1 << (j - 1))]);
}
}
}
}
int res = INF;
for(int i = 0; i <= (1LL << z2) - 1; i++){
bool ok = true;
for(int j = 1; j <= z2; j++){
if((i >> (j - 1) & 1) == 0){
for(int k = 1; k <= z2; k++){
if((i >> (k - 1) & 1) == 0 && a[z1 + j][z1 + k]){
ok = false;
}
}
}
}
if(!ok) continue;
int cur = 0, should = 0;
for(int j = 1; j <= z2; j++){
if((i >> (j - 1) & 1)){
cur += cost[z1 + j];
} else{
for(int k = 1; k <= z1; k++){
if(a[k][j + z1]){
should |= (1LL << (k - 1));
}
}
}
}
res = min(res, cur + dp[should]);
}
cout << res << '\n';
return 0;
}