Luogu P3943 星空 (差分思想 + 背包 + 状压DP)
差分是个常用的技巧,翻转连续的一段序列很容易想到翻转异或差分序列上的两个数。
差分序列上最多有个,并且肯定是偶数个。每次可以消除两个,消除的代价与这两个的距离相关,求将所有消除的最小代价。
消除的代价用完全背包或者都可以求出。用完全背包求消除代价,相当于把每个可以翻转的长度,看做体积分别为和的两个物品。
只有个需要翻转的,直接状态压缩即可。
每次转移时可以先固定选择最小的位置,然后再枚举另一个位置。
时间复杂度。
CODE
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 4e4 + 5;
const int MAXS = (1<<16) + 5;
int n, k, m, t, p[16], b[65], w[MAXN], f[MAXS], cnt[MAXS], s;
int main()
{
scanf("%d%d%d", &n, &k, &m); ++n;
for (int i = k, j = -1, x; i--; j=x) {
scanf("%d", &x);
if(x == j+1) p[t-1] = x+1;
else p[t++]=x, p[t++] = x+1;
}
memset(w,63,sizeof(w)), w[0] = 0;
for(int i = 1; i <= m; ++i)
scanf("%d", &b[i]);
for(int i = 1; i <= m; ++i)
for(int j = b[i]; j <= n; ++j)
w[j] = min(w[j], w[j-b[i]]+1);
for(int i = 1; i <= m; ++i)
for(int j = n-b[i]; j >= 0; --j)
w[j] = min(w[j], w[j+b[i]]+1);
memset(f,63,sizeof(f)), f[0]=0;
s = (1<<t)-1;
for(int i = 1; i <= s; ++i) {
if((cnt[i]=cnt[i>>1]+(i&1))&1) continue;
int j = 0, x; while(!((i>>j)&1))++j;
x=i^(1<<j);
for(int k = j+1; k <= t-1; ++k)
if((x>>k)&1) f[i] = min(f[i], f[x^(1<<k)] + w[p[k]-p[j]]);
}
printf("%d", f[s]);
return 0;
}