P9017 题解
更好的阅读体验
第一篇蓝题题解
题意简化
有
思考算法
先来看数据范围
如果选择预处理灯的所有状态以及开关的所有状态,总状态数就是
问题来了:我们预处理灯的状态还是开关的状态呢?很显然是灯的状态,因为最后要求把灯都关掉,开关关不关无所谓。
深度分析
可是不难发现开关的状态对最少步数也有影响,我们考虑把开关的影响消去。并且,预处理灯的状态时需要设定一个开关的状态才可以,姑且先把开关的状态全部设成
我们发现操作二和操作三是自动的,即不需要选择的。为了消除影响,要把我的开关和实际开关联系起来。
于是把
但是我们仍然需要认识到对我的开关进行单点修改相当于对实际开关进行单点修改。(理解不了看完后面的再来看可能更好理解,不懂的话先忽略)
具体操作
每一轮先用实际开关,
可是这样每一轮还是很棘手,我们发现对我的开关进行单点修改操作可以留着想做的时候一起做就行了,预处理一下就完事儿,预处理的东西就是灯的状态为
For example:
Example 1:
000
101
初始时我们先看灯的状态为 000,开关的状态为 000 时,能否
Example 2:
101
100
先看灯的状态为 101,开关的状态还是 000 时(下文省略不写),能否用 001,开关变成了 010。第一轮的操作我们就留着了。
第二轮时我们先看把之前的操作都用上能否关掉。即灯为 001 时能不能一步关掉,发现可以,那么就把当初的单点修改做了。答案为轮数减一,
(注:此时若想一步用 000 开关关掉 001 仅需要给第三个开关做单点修改。那么就相当于给实际开关第三个地方做单点修改啦!就是在第一步时把实际开关变成 101)
如果你觉得有一点理解了,那么再来看最后这个:
Example 3:
1000
1011
可以自己先模拟一下,再来看我的过程。
首先看灯的状态为 1000 能否 0011,实际开关旋转后变成 1101,进行第二轮。
第二轮开始前看一下灯的状态为 0011 能否 1110,实际开关旋转后变成 1110。
第三轮之前再看下灯为 1110,能否
补充:方法是先单点修改第一个开关,然后灯变成 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;
}