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;
}
posted @ 2018-06-28 12:00  Jerx2y  阅读(151)  评论(0编辑  收藏  举报