开关灯

开 关 灯 开关灯

题目链接: j z o j   3926 jzoj\ 3926 jzoj 3926

题目

在一个庆典上, n ∗ L n * L nL盏灯摆成了一个 n n n L L L列的长方形。起初,有些灯是亮的,有些灯是灭的。每列的灯是串联在一起的。一共有 L L L个开关,按下第 i i i个开关可以改变第 i i i列所有灯的状态,即这一列亮的灯熄灭,熄灭的灯点亮。而且,不同的行是可以随意交换的。
主办方希望所有灯以亮或暗的不同状态组成一个图案。请你计算能否通过控制开关形成这个图案。如果可以,请计算最少按多少次开关。

输入

第一行有一个整数 T T T,表示有多少组测试数据。
每组测试数据包含三行。第一行为两个整数 n n n , L L L
每组数据的第二行为 n n n个长度为 L L L 0 / 1 0/1 0/1字符串,依次描述起初每行的灯的开关状态。第 i i i个字符串的第 j j j个字符若是 ’ 1 ’ ’1’ 1,表示对应位置的灯是亮的; ’ 0 ’ ’0’ 0表示是灭的。
每组数据的第三行为 n n n个长度为 L L L 0 / 1 0/1 0/1字符串,描述主办方希望达到的所有灯的开关状态。格式同上。

输出

输出 T T T行,依次为每组测试数据的答案。如果不可能达到,输出 ” I m p o s s i b l e ” ”Impossible” Impossible;否则输出最少按多少次开关。

样例输入

3
3 2
01 11 10
11 00 10
2 3
101 111
010 001
2 2
01 10
10 01

样例输出

1
Impossible
0

样例解释

第一组测试数据,按第 2 2 2列开关,得到 00 00 00 , 10 10 10 , 11 11 11,然后依次交换后两行和前两行即可。
第二组测试数据,可以证明不可能达到要求的方案。
第三组测试数据,只需交换两行即可。

数据范围

40 % 40\% 40% 的数据: 1  ⁣ < =  ⁣ N , L  ⁣ < =  ⁣ 10. 1\! <=\! N, L\! <=\! 10. 1<=N,L<=10.
100 % 100\% 100% 的数据: 1  ⁣ < =  ⁣ N  ⁣ < =  ⁣ 150 1\! <=\! N\! <=\! 150 1<=N<=150 1  ⁣ < =  ⁣ L  ⁣ < =  ⁣ 50 1\! <=\! L\! <=\! 50 1<=L<=50 1  ⁣ < =  ⁣ T  ⁣ < =  ⁣ 4. 1\! <=\! T\! <=\! 4. 1<=T<=4.

思路

这道题要用二进制来做。

我们可以先求出每一行的 1 / 0 1/0 1/0数组换成十进制是多少(用 l o n g   l o n g long\ long long long来存),然后我们先枚举原来数组的第一行用最后对应第几行,然后异或出它们的值。这个值就是有哪些不同的地方,即若这个数在二进制下的第 i i i位是 1 1 1则表示第 i i i列要按。
那我们就对于每一次枚举,就再枚举剩下的数,将它们配对。若不能配对,则这样按不行。若能配对,则找出能配对中所要按次数最少的那一次,就是答案了。

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long

using namespace std;

ll T, n, l, a[151], b[151], ans;
bool in[151];
char c;

ll getnum(ll x) {//得到二进制
	if (!x) return 1;
	ll sum = getnum(x / 2);
	if (x % 2 == 0) return sum * sum;
	return sum * sum * 2;
}

ll getans(ll x) {//得到要按的个数
	ll sum = 0;
	while (x) {
		if (x % 2 == 1) sum++;
		x /= 2;
	}
	return sum;
}

int main() {
	scanf("%d", &T);//读入
	
	for (int t = 1; t <= T; t++) {
		memset(a, 0, sizeof(a));//初始化
		memset(b, 0, sizeof(b));
		ans = 2147483647;
		
		scanf("%lld %lld", &n, &l);
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= l; j++) {
				c = getchar();
				while (c != '1' && c != '0') c = getchar();
				if (c == '1') a[i] += getnum(l - j);//用二进制存
			}
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= l; j++) {
				c = getchar();
				while (c != '1' && c != '0') c = getchar();
				if (c == '1') b[i] += getnum(l - j);//这个也是用二进制存
			}
		
		for (int i = 1; i <= n; i++) {//找和谁匹配
			bool yes = 0;
			ll dif = a[1] ^ b[i];//异或
			memset(in, 0, sizeof(in));
			in[i] = 1;
			
			for (int j = 2; j <= n; j++) {
				yes = 1;
				for (int k = 1; k <= n; k++)
					if (!in[k] && (ll)(a[j] ^ b[k]) == dif) {//配对
						yes = 0;
						in[k] = 1;
						break;
					}
				if (yes) break;//没得配对
			}
			if (yes) continue;
			
			ans = min(ans, getans(dif));//求出最少要按多少次
		}
		
		if (ans == 2147483647) printf("Impossible\n");//一定按不出来
			else printf("%lld\n", ans);//输出
	}
	
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值