1 条题解

  • 0
    @ 2025-8-24 22:44:37

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar Xy_top
    constructive thinking

    搬运于2025-08-24 22:44:37,当前版本为作者最后更新于2023-02-05 11:46:53,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    更好的阅读体验

    第一篇蓝题题解

    题意简化

    nn 个开关分别对应 nn 盏灯,开关的状态有两种:激活,失活。灯的状态也有两种:开,关。现在你想要进行多轮操作后把灯全部关掉,你的操作如下:先选择一个开关的位置,将它取反(原先激活变成失活,原先失活变成激活)。选择完开关位置后将所有激活的开关按下,取反它对应的灯。取反灯之后所有开关进入旋转,最后一个开关到第一个。(原先开关假设为 S1S_1S2SnS_2\cdots S_n,旋转后变成 SnS_nS1S_1S2Sn1S_2\cdots S_{n-1}

    思考算法

    先来看数据范围 T2105T \leq 2\cdot 10^5n20n\leq 20,看到灯的 0101 状态,差不多就是状压啦。再一看 T2105T\leq 2\cdot 10^5,那肯定是预处理啦!

    如果选择预处理灯的所有状态以及开关的所有状态,总状态数就是 O(240)O(2^{40}) 爆炸,并且大部分的还用不到,那么我们就考虑只预处理其中一个。

    问题来了:我们预处理灯的状态还是开关的状态呢?很显然是灯的状态,因为最后要求把灯都关掉,开关关不关无所谓。

    深度分析

    可是不难发现开关的状态对最少步数也有影响,我们考虑把开关的影响消去。并且,预处理灯的状态时需要设定一个开关的状态才可以,姑且先把开关的状态全部设成 00。把这个开关叫做我的开关。

    我们发现操作二和操作三是自动的,即不需要选择的。为了消除影响,要把我的开关和实际开关联系起来。

    于是把 112233 操作分开来处理。我的开关用来做 11 操作(它也会改变灯的状态,也会旋转。),实际开关每一轮自动做 2233 操作。

    但是我们仍然需要认识到对我的开关进行单点修改相当于对实际开关进行单点修改。(理解不了看完后面的再来看可能更好理解,不懂的话先忽略)

    具体操作

    每一轮先用实际开关,22 操作改变灯。然后再看我的开关,通过 22 操作再改变灯,每一轮开始时我们都可以改变我的开关的一个位置的状态。此时发现我的开关初始时全为 00 是对的,无法对灯进行任何操作(因为我们还没做 11 操作),每一轮结束后进行旋转操作。

    可是这样每一轮还是很棘手,我们发现对我的开关进行单点修改操作可以留着想做的时候一起做就行了,预处理一下就完事儿,预处理的东西就是灯的状态为 ii 时,开关状态为全 0 时能否在 jj 步之内关掉灯。时间复杂度 O(n2×2n+T×n2)O(n^2\times 2^n + T\times n^2)。没看懂?没事儿,确实有点难理解。

    For example:

    Example 1:

    000
    101
    

    初始时我们先看灯的状态为 000,开关的状态为 000 时,能否 00 步搞定(注意:步数必须要全部用完,不能少用),发现可以,答案为当前轮数减一,00

    Example 2:

    101
    100
    

    先看灯的状态为 101,开关的状态还是 000 时(下文省略不写),能否用 00 步搞定,不可以。先用实际开关操作灯,此时灯变成 001,开关变成了 010。第一轮的操作我们就留着了。

    第二轮时我们先看把之前的操作都用上能否关掉。即灯为 001 时能不能一步关掉,发现可以,那么就把当初的单点修改做了。答案为轮数减一,11

    (注:此时若想一步用 000 开关关掉 001 仅需要给第三个开关做单点修改。那么就相当于给实际开关第三个地方做单点修改啦!就是在第一步时把实际开关变成 101

    如果你觉得有一点理解了,那么再来看最后这个:

    Example 3:

    1000
    1011
    

    可以自己先模拟一下,再来看我的过程。

    首先看灯的状态为 1000 能否 00 步搞定?不可以,那么用实际开关对灯进行操作,变成 0011,实际开关旋转后变成 1101,进行第二轮。

    第二轮开始前看一下灯的状态为 0011 能否 11 步搞定,发现还是不行,继续操作然后旋转。灯变成 1110,实际开关旋转后变成 1110

    第三轮之前再看下灯为 1110,能否 22 步搞定,可以。答案为轮数减一,22

    补充:方法是先单点修改第一个开关,然后灯变成 0110,开关旋转变成 0100,再单点修改第三个就行。把对我的开关的操作移动到实际开关上看一看?第一轮单点修改第一个,变成 0011,灯变成 1011,开关旋转后变成 1001,接着第二轮修改第三个,变成 1011,正好搞定,我们的方法是对的。

    talk is cheap, show me the code:

    #include <iostream>
    using namespace std;
    string s,t;
    int T, n;
    int g[45][22], mask[22];
    bool f[45][1 << 21];
    int calc (int s, int t) {
        if (s == 0) return 0;
        for (int i = 1; i <= 2 * n; i ++) {
            int vis = s;
            for (int j = 0; j < n; j ++) if (t & (1 << j) ) vis = vis ^ g[i][j];
            if (f[i][vis]) return i;
        }
    }
    int main() {
        f[0][0] = 1;
        cin >> T >> n;
        for (int i = 0; i <= n; i ++) mask[i] = 1 << i;
        int N = 1 << n;
        for (int k = 0; k < n; k ++)
            for (int i = 1; i <= 2 * n; i ++)
                for (int j = 0; j < i; j ++) g[i][k] ^= mask[(k + j) % n];
        for (int i = 1; i <= 2 * n; i ++) for (int j = 0; j < N; j ++) {
                if (!f[i - 1][j]) continue;
                for (int k = 0; k < n; k ++){
                    int vis = j;
                    vis ^= g[i][k];
                    f[i][vis] = 1;
                }
            }
        while (T --){
            cin >> s >> t;
            int S = 0, T = 0;
            for (int i = 0; i < n; i ++) {
                if (s[i] == '1') S ^= (1 << i);
                if (t[i] == '1') T ^= (1 << i);
            }
            cout << calc(S, T) << "\n";
        }
        return 0;
    }
    
    • 1

    信息

    ID
    8306
    时间
    4000ms
    内存
    256MiB
    难度
    5
    标签
    递交数
    0
    已通过
    0
    上传者