洛谷p3943 星空 题解(奇妙转化+状压)

传送门

感觉真的是一道挺有质量的好题,练到了。

我就不啰嗦部分分了,直接说正解吧。

第一印象:数据范围 k 8 k\leq8 标签上说也许是个状压dp

正式思考:首先发现一个事情:把一段区间取反非常麻烦。我们可以考虑类似差分的思想,处理出 d [ i ] = a [ i ]   x o r   a [ i + 1 ] d[i]=a[i]\ xor\ a[i + 1] ,于是将区间取反就变成了 d d 上的两个端点取反。

那么问题就转化为:给定一个 01 01 串和一个距离集合 L L ,每次可以选两个位置 i i j j 将它们取反,但是必须满足 j i L |j-i|\in L ,问最少要操作多少次才能把它们全部变成1。

考虑将两个位置上的数取反:

  • 两个都是1:显然这样的操作没有任何意义
  • 一个0一个1:相当于把0移动到了1的位置
  • 两个都是0:相当于将它们消去,但是也可以看做先把其中一个移动到另一个的位置然后消去。

所以问题又转化为:一个 n n 个顶点的无向图,有不超过 2 k 2k 个点上有妹子;每次可以让一个点上的妹子走到另一个点,但是走的距离有限制;两个妹子碰到一起就都是你的了;问至少要操作多少次才能泡遍所有妹子。皮这一下十分开心

要让两个妹子走到一起并且操作次数尽量少,显然要走最短路。所以就先spfa(或者可以看做一个简单bfs)求出每个有妹子的点到其它所有点的最短距离。复杂度 O ( n m k ) O(nmk)

于是问题最终转化为:有不超过 2 k 2k 个物品,选择其中两个可以花一定代价消去,问最小代价和。

简单状压dp即可。

有一个小细节需要注意:对于当前状态 S S ,可以枚举两个不在 S S 中的物品 i , j i,j 进行拓展,如果直接这样枚举复杂度是 O ( k 2 2 2 k ) O(k^22^{2k}) 的,在某些题中可能被卡(本题应该可以过)。一个常见优化是,物品 i i 就直接选择不在 S S 中的编号最小的物品,而不是枚举 i i ,这样的话复杂度就是 O ( k 2 2 k ) O(k*2^{2k})

#include <cctype>
#include <cstdio>
#include <climits>
#include <algorithm>
#include <queue>

template <typename T> inline void read(T& t) {
    int f = 0, c = getchar(); t = 0;
    while (!isdigit(c)) f |= c == '-', c = getchar();
    while (isdigit(c)) t = t * 10 + c - 48, c = getchar();
    if (f) t = -t;
}
inline int read() {
    int x; read(x); return x;
}
#if __cplusplus >= 201103L
template <typename T, typename... Args>
inline void read(T& t, Args&... args) {
    read(t); read(args...);
}
#else
template <typename T1, typename T2>
inline void read(T1& t1, T2& t2) { read(t1); read(t2); }
template <typename T1, typename T2, typename T3>
inline void read(T1& t1, T2& t2, T3& t3) { read(t1, t2); read(t3); }
template <typename T1, typename T2, typename T3, typename T4>
inline void read(T1& t1, T2& t2, T3& t3, T4& t4) { read(t1, t2, t3); read(t4); }
template <typename T1, typename T2, typename T3, typename T4, typename T5>
inline void read(T1& t1, T2& t2, T3& t3, T4& t4, T5& t5) { read(t1, t2, t3, t4); read(t5); }
#endif	// C++11

#ifdef WIN32
#define LLIO "%I64d"
#else
#define LLIO "%lld"
#endif	// WIN32 long long
#define rep(I, A, B) for (int I = (A); I <= (B); ++I)
#define rrep(I, A, B) for (int I = (A); I >= (B); --I)
#define erep(I, X) for (int I = head[X]; I; I = next[I])

const int maxn = 4e4 + 207;
const int inf = INT_MAX >> 3;
int a[maxn], b[maxn];
int dist[20][maxn], pos[maxn];
int dp[1 << 18];
int n, m, k, zero;

inline void spfa(int ss) {
    int s = pos[ss];
    rep(i, 0, n) dist[ss][i] = inf;
    dist[ss][s] = 0;
    std::queue<int> q; q.push(s);
    while (!q.empty()) {
        int x = q.front(); q.pop();
        rep(i, 1, m) {
            if (x + b[i] <= n && dist[ss][x + b[i]] > dist[ss][x] + 1)
                q.push(x + b[i]), dist[ss][x + b[i]] = dist[ss][x] + 1;
            if (x - b[i] >= 0 && dist[ss][x - b[i]] > dist[ss][x] + 1)
                q.push(x - b[i]), dist[ss][x - b[i]] = dist[ss][x] + 1;
        }
    }
}

void dfs(int S) {
    int i = 1;
    while (1 << (i - 1) & S) ++i;
    rep(j, i + 1, zero) if (!(1 << (j - 1) & S)) {
        int T = S | (1 << (i - 1)) | (1 << (j - 1));
        if (dp[T] > dp[S] + dist[i][pos[j]]) {
            dp[T] = dp[S] + dist[i][pos[j]];
            dfs(T);
        }
    }
}

int main() {
    read(n, k, m);
    rep(i, 1, k) a[read()] = 1;
    rep(i, 1, m) read(b[i]);
    rep(i, 0, n) if (a[i] ^ a[i + 1]) pos[++zero] = i;
    rep(i, 1, zero) spfa(i);
    rep(i, 1, (1 << zero) - 1) dp[i] = inf;
    dfs(0);
    printf("%d\n", dp[(1 << zero) - 1]);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_39677783/article/details/82919696