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

传送门

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

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

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

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

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

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

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

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

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

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

简单状压dp即可。

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

#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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值