usaco2.2 lamps

题目链接


(最近以来好不容易让我感到自己智商上线的一道题(叹


题意:

最初的状态是 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);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值