首页 > 其他分享 >Solution of USACO 2023.Jan Au T2 Light Off

Solution of USACO 2023.Jan Au T2 Light Off

时间:2023-02-05 11:56:50浏览次数:49  
标签:状态 Off int Light T2 开关 操作 单点 变成

好久不更了,祝大家新年快乐,元宵快乐,兔飞猛进,钱兔无量(迟到了 $n$ 天的祝福)

找到一个很不错的题,写一下题解。

题目链接

$Au$ 组,感觉就 $T2$ 值得一讲,其他的题目没什么意义,都比较简单。

题意简化

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

思考算法

先来看数据范围 $T \leq 2\cdot 10^5$,$n\leq 20$,看到灯的 $01$ 状态,感觉是状压。再一看 $T\leq 2\cdot 10^5$,那肯定是预处理啦!

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

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

深度分析

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

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

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

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

具体操作

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

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

For example:

Example 1:

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

Example 2:

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

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

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

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

Example 3:

1000
1011

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

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

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

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

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

 

后话:官方题解加了个优化,去掉了个 $O(n)$,神啊!我来大概说一下。假如灯的状态 0100 是可以用 $j$ 步搞定的,那么 0100 能旋转到的所有的状态都可以用 $j$ 步搞定啦!状态数量直接除以 $n$。

(话说这题时限为啥不开到 $1s$ 坑死我们老师呢。)

 

因为我懒,所以给个 $O(n^2\times 2^n + T\times n^2)$ 的代码:(非本人手写,不过不到 $1k$)

#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;
}

 

 

  

标签:状态,Off,int,Light,T2,开关,操作,单点,变成
From: https://www.cnblogs.com/Xy-top/p/17093107.html

相关文章