彩灯
题目链接:ybt金牌导航8-1-1 / luogu P3857
题目大意
有一些灯,然后有一些按钮。
某一个按钮能使某些灯泡改变亮灭状态。
然后问你可以弄出多少种这一排灯的不同状态。
(不同状态:只需要有一个灯泡的亮灭情况不同就可以)
思路
这题一开始看到题目,我们会想到用状压 dp 来做。
就 (f_{i,j}) 表示前 (i) 个按钮,灯的状态是 (j) 的情况是否存在。
但你你一看数据范围,(nleq 50),状压不行。
那你考虑用别的方法。
那你按按钮相当于异或,异或什么呢?异或你能控制的灯组成的二进制数。
那你异或数字,就会想到用线性基来做。
线性基是什么呢?
它是一个集合,它集合里的数可以选择一部分异或起来得到你原来的集合中的所有数。
然后它无论怎么选,都不会异或出 (0)。
那你会想到,它的个数一定是小于等于你原集合的个数的。
然后你考虑如何构造出这个东西。
你可以考虑每次多加一个数,会对原来的线性基发生什么样的改变。
我们先规定,(f_i) 为二进制中最高位的 (1) 是第 (i) 位的线性基。
那我们尝试插入一个数,我们从高位到低位枚举这个数二进制中的每个 (1)。然后如果对于你找到的位数 (i),如果 (f_i=0),也就是没有数,那就插入你现在的数。如果有数,那你就开始考虑怎么办。
因为你一定要存在一种方法能构造出来这个数,但是你这一位已经有数了,就算你在后面硬插入这个数,也会因为异或使得这一位变成 (0),就可能造成整个数列都是 (0)。
那你就试着让线性基中的两个数构造出这个数,那很明显你现在这一个的数是要选的,那另一个就是它异或你现在的数。
那你就把你现在的数异或上 (f_i),然后继续找下去。
可以看出,这样是可行的,而且 (f_i) 是唯一的。
那你构造出来线性基之后,你就考虑通过这些线性基,可以构造出来多少个数。
那就是 (2^k),(k) 是线性基的大小。
代码
#include<cstdio>
#define mo 2008
#define ll long long
using namespace std;
int n, m;
ll now, f[51], ans;
char c[101];
void add(ll x) {
for (int i = n; i >= 1; i--) {
if ((x >> i) & 1ll) {//从高到低位枚举这个数的 1(二进制中)
if (!f[i]) {//线性基中是空的,就可以直接存入走人
f[i] = x;
return ;
}
else x ^= f[i];//不是空的,那如果非要用这个数,下面的就要它跟这个数异或的结果
//因为你把它异或了,后面插这个,才能用异或的和它异或出你现在的数
}
}
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= m; i++) {
scanf("%s", c + 1);
now = 0;
for (int j = 1; j <= n; j++)
if (c[j] == 'O') now |= 1ll << j;//把它二进制对于的数还原出来
add(now);//往线性基里面新放数
}
ans = 1;
for (int i = 1; i <= n; i++)
if (f[i])//统计哪一位可以选亮不亮
ans = (ans << 1ll) % mo;//可以亮或者不亮
printf("%lld", ans);
return 0;
}