密码锁题解

题目

hzwer 有一把密码锁,由 N 个开关组成。一开始的时候,所有开关都是关上的。当且仅 当开关 x1,x2,x3,…xk 为开,其他开关为关时,密码锁才会打开。
他可以进行 M 种的操作,每种操作有一个 size[i],表示,假如他选择了第 i 种的操作的话,他可以任意选择连续的 size[i]个格子,把它们全部取反。(注意,由于黄金大神非常的神,所以操作次数可以无限><).
本来这是一个无关紧要的问题,但是,黄金大神不小心他的钱丢进去了,没有的钱他哪里能逃过被 chenzeyu97 NTR 的命运?>
< .于是,他为了虐爆 czy,也为了去泡更多的妹子,决定打开这把锁。但是他那么神的人根本不屑这种水题。于是,他找到了你。你的任务很简单,求出最少需要多少步才能打开密码锁,或者如果无解的话,请输出-1。

输入
第 1 行,三个正整数 N,K,M,如题目所述。
第 2 行,K 个正整数,表示开关 x1,x2,x3…xk 必须为开,保证 x 两两不同。第三行,M 个正整数,表示 size[i],size[]可能有重复元素。
输出
输出答案,无解输出-1
样例输入 Copy
10 8 2
1 2 3 5 6 7 8 9
3 5
样例输出 Copy
2
提示
样例输入2:
3 2 1
1 2
3
样例输出2:
-1
对于 50%的数据,1≤N≤20,1≤k≤5,1≤m≤3;
对于另外 20%的数据,1≤N≤10000,1≤k≤5,1≤m≤30;
对于 100%的数据,1≤N≤10000,1≤k≤10,1≤m≤100;

前言:

和网上的思路差不多。(或者说就是网上的思路)

想法:

50pts

很容易想到用二进制来表示灯的状态。

由于 n &lt; = 20 n&lt;=20 n<=20,所以 2 20 2^{20} 220的状态完全可行。

  • 状压dp

f [ s ] f[s] f[s]表示灯的状态为 s s s的时候用的最少步数,转移就枚举用的是哪种操作,适用到哪个区间。 O ( 2 n n m ) O(2^nnm) O(2nnm) 勉强。

  • bfs

建出 2 20 2^{20} 220个点,在这张图上瞎走走。。复杂度差不多吧。

100pts

区间取反=区间 x o r   1 xor\ 1 xor 1=区间 m o d   2 mod\ 2 mod 2意义下加法

考虑把操作倒过来不影响答案。

把原来的 k k k个关键点在原来的图上差分,变成 2 k 2k 2k个点。

现在的操作就是同时倒置两个点,借用其他人的说法,把原始的点看作石子。

我们的任务是消石子,消成全 0 0 0

如果两个点以前都没有石子,不操作。因为操作会增加偶数个石子,一定不利。

如果一个点有石子,一个点没有。相当于交换石子的位置。

如果两个点都有石子,相当于抵消了。(兑子)

所以,可以 b f s bfs bfs预处理出 d i s [ i ] [ j ] dis[i][j] dis[i][j]表示 i i i这个位置的石子到 j j j位置的距离。

一般图最小权最大匹配

  • 带花树

不会

  • 状压dp

f [ s ] f[s] f[s]表示点的状态为 s s s的时候的最小步数。总点数为 t o t tot tot的话,一开始 f [ ( 1 &lt; &lt; t o t ) − 1 ] = 0 f[(1&lt;&lt;tot)-1]=0 f[(1<<tot)1]=0

转移就枚举当前位置最小的一个石子,枚举它是交换到另外一个空地方还是与其他一个棋子兑掉了。 都可以用异或很简单地实现。

O ( k n m + 2 2 k k ) O(knm + 2^{2k}k) O(knm+22kk)

#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
const int N = 2e4 + 5, K = 24, M = 105, INF = 0x3f3f3f3f;
int f[1<<K];
struct poi {
    int id, x;
}c[K];
int siz[M], a[K], exi[N], val[N], vis[N], dis[K][K], dist[N], tot;
int n, k, m;
queue<int>q;
inline void bfs(int s) {
    while (!q.empty())q.pop();
    memset(vis, 0, sizeof vis);
    memset(dist, 0x3f, sizeof dist);
    q.push(c[s].x); vis[ c[s].x ] = 1; dist[ c[s].x ] = 0;
    while (!q.empty()) {
        int cur = q.front(); q.pop();
        for (int i = 1; i <= m; ++i) {
            int nxtx = cur - siz[i];
            if (nxtx > 0 && !vis[nxtx]) {
                vis[nxtx] = 1; dist[nxtx] = dist[cur] + 1;
                q.push(nxtx);
            }
            nxtx = cur + siz[i];
            if (nxtx <= n + 1 && !vis[nxtx]) {
                vis[nxtx] = 1; dist[nxtx] = dist[cur] + 1;
                q.push(nxtx);
            }
        }
    }
    for (int i = 1; i <= tot; ++i) dis[s][i] = dist[ c[i].x ];
}
int main() {
    scanf("%d %d %d", &n, &k, &m);
    for (int i = 1; i <= k; ++i) {
        scanf("%d", &a[i]);
        exi[ a[i] ] = 1;
    }
    for (int i = 1; i <= k; ++i) {
        val[ a[i] ] = 1 - exi[ a[i] - 1];
        val[ a[i] + 1] = exi[ a[i] + 1] - 1;
    }
    for (int i = 1; i <= n + 1; ++i) {
        if (val[i] != 0) {
            c[++tot].id = tot;
            c[tot].x = i;
        }
    }
    for (int i = 1; i <= m; ++i)scanf("%d", &siz[i]);
    for (int i = 1; i <= tot; ++i) bfs(i);
    memset(f, 0x3f, sizeof f);
    f[(1<<tot) - 1] = 0;
    for (int i = (1 << tot) - 1; i >= 0; --i) {
        int pos = 0;
        for (int j = 1; j <= tot; ++j) {
            if ((i >> (j - 1)) & 1) {
                pos = j;
                break;
            }
        }
        if (pos) {
            for (int j = 1; j <= tot; ++j) {
                int nxt = i ^ (1 << (j - 1)) ^ (1 << (pos - 1));
                f[nxt] = min(f[nxt], f[i] + dis[pos][j]);
            }
        }
    }
    if (f[0] != INF) printf("%d\n", f[0]);
    else puts("-1");
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值