(最近以来好不容易让我感到自己智商上线的一道题(叹
题意:
最初的状态是 1-N 灯全开着
接下来进行一系列(4种)操作(操作次数 C 多达 1w)
1:将所有灯的状态反转
2:将偶数号的状态反转
3:将奇数号的状态反转
4:将 3k+1 号的状态反转
得到最终状态
现给出最终状态中的 一些 灯的状态
求所有可能的最终状态,并升序排列
思路:
无脑递归显然不可行,10000 层递归显然是开玩笑
那么肯定有什么聪明的办法
显然,操作的最终状态与操作顺序无关
设四种步骤的操作数分别为 a, b, c, d
则我们有
a * step1 + b * step2 + c* step3 + d * step4 == FinalState - InitialState;
a + b + c + d == n;
(stepi 都是 01 位串
(第一个等式意思意思写得不严谨,大概含义就是 每一位上 进行的 所有的操作 累加起来的 1 的总和(模上一个2) 导致的这一位发生的改变,即为最终状态与起始状态之间的改变((当然只需要检查那些给定状态的位
写到这里我们很容易就能发现,对于每个 stepi,一旦进行两次,%2 之后改变量就全是 0,就相当于什么都没做
所以我们可以将第一个式子变为
a%2 * step1 + b%2 * step2 + c%2 * step3 + d%2 * step4 == FinalState - InitialState;
也就是说,对于每一个 stepi 都只有进行和不进行的两种情况,总共也就只有 2^4 = 16 种情况
另外因为我们是两个两个抵消的,还要满足的一个要求就是 (n - ∑ step) % 2 == 0; 或者也可以 ∑ step <= n;
说到这里,怎么做就很显然了
AC代码如下:
(末尾有和 ANALYSIS 的对比反思)
/*
PROB: lamps
LANG: C++
ID: fan_0111
*/
#include <iostream>
#include <cstdio>
#include <string>
#include <algorithm>
using namespace std;
int pos[110], line[110], sum[110], change[4][110], step[4], ans[110];
string s[100010];
int tot = 0, c, n;
void in(int state) {
int x;
while (cin >> x && x != -1) {
pos[tot++] = x;
line[x] = state;
}
}
bool check1() {
for (int i = 0; i < tot; ++i) {
if (line[pos[i]] != (sum[pos[i]] & 1)) return false;
}
return true;
}
bool check2() {
int Step = step[0] + step[1] + step[2] + step[3];
return !(((c - step[0] - step[1] - step[2] - step[3]) & 1) || Step > c);
}
void modify(int x, int sign) {
for (int i = 1; i <= n; ++i) {
sum[i] += change[x][i] * sign;
}
}
void dfs(int x, int choice, int& totAns) {
step[x] = choice;
if (x == 3) {
if (check1() && check2()) {
for (int i = 1; i <= n; ++i) ans[i] = 1;
for (int i = 0; i < 4; ++i) {
if (step[i] == 0) continue;
for (int j = 1; j <= n; ++j) {
ans[j] = (ans[j] + change[i][j]) % 2;
}
}
for (int i = 1; i <= n; ++i) {
s[totAns] += ans[i] + '0';
}
++totAns;
}
return;
}
dfs(x+1, 0, totAns);
modify(x+1, 1);
dfs(x+1, 1, totAns);
modify(x+1, -1);
}
int main() {
freopen("lamps.in", "r", stdin);
freopen("lamps.out", "w", stdout);
cin >> n >> c;
in(0);
in(1);
sort(pos, pos+tot);
for (int i = 1; i <= n; ++i) change[0][i] = 1;
for (int i = 1; i <= n; i += 2) change[1][i] = 1;
for (int i = 2; i <= n; i += 2) change[2][i] = 1;
for (int i = 1; i <= n; i += 3) change[3][i] = 1;
int totAns = 0;
dfs(0, 0, totAns);
modify(0, 1);
dfs(0, 1, totAns);
sort(s, s+totAns);
if (totAns == 0) cout << "IMPOSSIBLE\n";
else {
for (int i = 0; i < totAns; ++i) {
cout << s[i] << endl;
}
}
fclose(stdin);
fclose(stdout);
return 0;
}
后来看了 ANALYSIS, 发现除了这个以外,还可以有一个优化
那就是事实上我们这里的所有操作,产生的效果其实是 每 6 位一循环的
所以我们可以只看前 6 位的状态,而不是去看 N 位
这里的 N (10-100) 还比较小,如果大一点的话,可能就会被卡掉了
所以只看 6 位是一个很有意义的优化
并且 ANALYSIS 里的代码用了很多位运算
比我用数组去存很多东西,最后对 01 位串的字符串去排序的做法高明了很多
很值得学习
还有就是 ANALYSIS 里的递归,也和我用的不一样
我是对每个 step 去递归取货不取,就像是 打印一个集合的全部子集 的做法一样
先对不取 dfs,加进了取的状况,再 dfs
而 ANALYSIS 里先是规定一些给定的步数情况(这是可以做到的),对还剩余的步骤数可以取什么进行 dfs,也是一个很常规的思路
总得来说觉得 ANALYSIS 里的代码写得还是很美的
附上 ANALYSIS 代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#define MAXLAMP 6
#define LAMPMASK ((1<<MAXLAMP)-1)
int nlamp;
int nswitch;
int ison;
int known;
int poss[1<<MAXLAMP];
int flip[4] = {
LAMPMASK, /* flip all lights */
LAMPMASK & 0xAA, /* flip odd lights */
LAMPMASK & 0x55, /* flip even lights */
LAMPMASK & ((1<<(MAXLAMP-1))|(1<<(MAXLAMP-4))) /* lights 1, 4 */
};
/*
* Starting with current light state ``lights'', flip exactly n switches
* with number >= i.
*/
void
search(int lights, int i, int n)
{
if(n == 0) {
if((lights & known) == ison)
poss[lights] = 1;
return;
}
for(; i<4; i++)
search(lights ^ flip[i], i+1, n-1);
}
void
printseq(FILE *fout, int lights)
{
int i;
char s[100+1];
for(i=0; i<nlamp; i++)
s[i] = (lights & (1<<(MAXLAMP-1 - i%MAXLAMP))) ? '1' : '0';
s[nlamp] = '\0';
fprintf(fout, "%s\n", s);
}
void
main(void)
{
FILE *fin, *fout;
int a, i, impossible;
fin = fopen("lamps.in", "r");
fout = fopen("lamps.out", "w");
assert(fin != NULL && fout != NULL);
fscanf(fin, "%d %d", &nlamp, &nswitch);
for(;;) {
fscanf(fin, "%d", &a);
if(a == -1)
break;
a = MAXLAMP-1 - (a-1) % MAXLAMP;
ison |= 1<<a;
known |= 1<<a;
}
for(;;) {
fscanf(fin, "%d", &a);
if(a == -1)
break;
a = MAXLAMP-1 - (a-1) % MAXLAMP;
assert((ison & (1<<a)) == 0);
known |= 1<<a;
}
if(nswitch > 4)
if(nswitch%2 == 0)
nswitch = 4;
else
nswitch = 3;
for(; nswitch >= 0; nswitch -= 2)
search(LAMPMASK, 0, nswitch);
impossible = 1;
for(i=0; i<(1<<MAXLAMP); i++) {
if(poss[i]) {
printseq(fout, i);
impossible = 0;
}
}
if(impossible)
fprintf(fout, "IMPOSSIBLE\n");
exit(0);
}