开关灯
jzoj 3926
题目大意
有一个n×l的灯网,你可以把按某一列的灯的按钮,然后这一列开的关,关的开,你也可以把某一行的灯和别的行的灯调换,问从当前状态到目标状态最少按多少个按钮
输入样例
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列开关,得到00, 10, 11,然后依次交换后两行和前两行即可。
第二组测试数据,可以证明不可能达到要求的方案。
第三组测试数据,只需交换两行即可。
数据范围
40% 的数据:
1
⩽
N
,
L
⩽
10.
1 \leqslant N, L \leqslant 10.
1⩽N,L⩽10.
100% 的数据:
1
⩽
N
⩽
150
,
1
⩽
L
⩽
50
,
1
⩽
T
⩽
4.
1 \leqslant N \leqslant 150,1 \leqslant L \leqslant 50,1 \leqslant T \leqslant 4.
1⩽N⩽150,1⩽L⩽50,1⩽T⩽4.
解题思路
我们枚举当前状态的第一行和目标状态的第几行配对,然后我们可以用异或求出某一行是否按,然后我们看看剩下的行是否能找到可配对的行,如果都有
那这就是一组解,然后求最小值即可
代码
#include<map>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
ll t, n, l, ans, g, pp, p[200], a[200], b[200];
string str;
ll js(ll x)//看有多少个1
{
ll sum = 0;
while(x) sum += x&1, x>>=1;
return sum;
}
int main()
{
scanf("%lld", &t);
while(t--)
{
memset(a, 0, sizeof(a));
memset(b, 0, sizeof(b));
scanf("%lld %lld", &n, &l);
for (ll i = 1; i <= n; ++i)
{
cin>>str;
for (ll j = 1, g = 1; j <= l; ++j, g <<= 1)
a[i] += (str[j - 1] - 48) * g;//转换为数字
}
for (ll i = 1; i <= n; ++i)
{
cin>>str;
for (ll j = 1, g = 1; j <= l; ++j, g <<= 1)
b[i] += (str[j - 1] - 48) * g;
}
ans = 51;
for (ll i = 1; i <= n; ++i)
{
memset(p, 0, sizeof(p));
g = a[1]^b[i];//那些要按
p[i] = 1;//是否用过
pp = 1;//是否找到配对的
for (ll j = 2; j <= n; ++j)
{
pp = 0;
for (ll k = 1; k <= n && !pp; ++k)
if (!p[k] && (a[j]^b[k]) == g)//未用过且可以配对
p[k] = 1, pp = 1;
if (!pp) break;
}
if (pp) ans = min(js(g), ans);//求最小值
}
if (ans == 51) printf("Impossible\n");
else printf("%lld\n", ans);
}
return 0;
}