Luogu P3943 星空 (差分思想 + 背包 + 状压DP)

差分是个常用的技巧,翻转连续的一段0101序列很容易想到翻转异或差分序列上的两个数。

差分序列上最多有2k2k11,并且肯定是偶数个。每次可以消除两个11,消除的代价与这两个11的距离相关,求将所有11消除的最小代价。

消除的代价用完全背包或者BFSBFS都可以求出。用完全背包求消除代价,相当于把每个可以翻转的长度xx,看做体积分别为xxx-x的两个物品。

只有T2k16T\leq 2k\leq 16个需要翻转的11,直接状态压缩DPDP即可。

每次转移时可以先固定选择最小的位置,然后再枚举另一个位置。

时间复杂度O(NM+2TT)\mathcal{O}(NM+2^T T)

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;
}
posted @ 2019-12-14 14:50  _Ark  阅读(100)  评论(0编辑  收藏  举报