Luogu3943 星空 题解 状压+差分
命运偷走如果 只留下结果
时间偷走初衷 只留下了苦衷
你来过 然后你走后 只留下星空
这道题的主要思路是差分+状压dp,不需要额外的毒瘤数据结构..
如果我们将原序列定义为暗灯的是1,亮灯为0,差分为数组\(book\),那么\(book\)中的1的个数一定是偶数个;
我们定义\(c_i\)为要使\(i\)个反转至少需要翻多少次(不可能则为\(INF\)),然后\(c\)数组可以用dp/递推预处理出来,
然后将原题转化成在差分数组中,每一次都可以选择两个距离为\(i\)的"1", 然后将他们以消耗\(c_i\)的代价变成0;
当然,可能对于两个值为1的位置\(c[i] == INF\)这样的话不能一次将2个1变为0,而是可以将另外的0变成1...
这个可以用状压DP来求解,设\(dp[S]\)为状态为\(S\)的时候最小需要翻的步数.
注意这里的S并不是表示的是差分数组每个位置的翻与否,而是表示的是位置为1的翻与否,0则是没有翻,而我们的目的是把这些是1的位置全都翻成0,所以最后的答案是将他们都翻:\(dp[(1<<cnt)-1]\).
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#define N 100005
#define M 128
using namespace std;
int n, K, m, a[N], b[N], book[N], f[N], c[N], Lg[N];
long long g[1 << 17];
inline int lowbit(int x) {
return x & (-x);
}
int main() {
cin >> n >> K >> m;
for (int i = 1; i <= K; ++i)
scanf("%d", a + i);
for (int i = 1; i <= m; ++i)
scanf("%d", b + i);
for (int i = 1; i <= K; ++i)
book[a[i]] ^= 1, book[a[i] + 1] ^= 1;
int cnt = 0;
for (int i = 1; i <= n + 1; ++i)
if (book[i]) c[++cnt] = i;
memset(f, 0x3f, sizeof f);
f[0] = 0;
for (int j = 1; j <= m; ++j)
for (int i = 1; i <= n; ++i)
if (i - b[j] >= 0)
f[i] = min(f[i], f[i - b[j]] + 1);
for (int j = 1; j <= m; ++j)
for (int i = n; i >= 1; --i)
if (i + b[j] <= n)
f[i] = min(f[i], f[i + b[j]] + 1);
for (int i = 1; i < (1 << cnt); ++i)
g[i] = 1e12;
g[0] = 0;
for (int S = 0; S < (1 << cnt); ++S) {
int u = 0;
while (S & (1 << u)) ++u;
for (int v = u + 1; v < cnt; ++v) {
if (((1 << v) & S)) continue;
long long &t = g[(S | (1 << u)) | (1 << v)];
t = min(t, g[S] + f[c[v + 1] - c[u + 1]]);
}
}
cout << g[(1 << cnt) - 1] << endl;
return 0;
}