题意:给出几排数字,代表着灯的情况。
要求让我们输出数字最小数字n,使这些灯可以区分开来。
做完刚看完一小章,子集神马的都没看到。今天看到这题就愣了。
我的思路一直停留在把每一行的前x个数字拿出来一一比较。。。。这样当然想不出来。
我把这题在CSDN上能找到的解题报告都看了一遍,都用到了子集生成,理解不能。
直到我看到了这么一个神思路!来自沐阳。ORZ
引用一下他的说明。
给定一个包含几个显示效果的显示器,众所周知,通过不同的组合可以产生不同的信息,但是有时候如果信息有限的情况下是可以不需要那么多的显示器的,比如只要求通过显示器来区分两个信号,那么只需要找到两者显示不同的那个显示器,开启这一台显示器,其亮或者是不亮来表示这两个信号。两个信号可以很简单的处理,但是一旦信号较多,处理起来就很麻烦了,不管你有没有想出一个好方法,反正我是没有。由于给定的数据范围比较小,这里就可以有一个暴力的方法,那就是先假设只有一个显示器,将其安放在不同的位置,看是否有满足题义的,然后就是两个显示器...... 这样下去知道满足题义为止,最后输出最少显示器的个数。计算的次数 [ C( P, 1 ) + C( P, 2 ) + ... + C( P, P ) ] * N; 时间复杂度为O( ( P ^ 2 - 1 ) * N ),这是能够处理掉的。这里有一个重要的思想就是状态压缩,由于每一位上只有0,1两个值,所以可以用一个 int 数在表示这个状态,比如 0 0 1 0 1,就可以表示为 5,P ^ 2 - 1 就用 for( 1 - 2 ^ P - 1 ) 来替代,每次操作用 & 来计算,因为这样可以模拟亮灯的过程,满足题义的数字再计算出他的含 1 位的个数,再选取最小的数即可
一开始看不懂,后来想了一节高数课和半节C++课(高数老师和C++老师别怪我),终于明白了他是什么意思。
①。因为给出的都是0 和 1的组合,所以可以把一组输入看成是一个二进制,且每组不同的数对应着各自、唯一的状态。
②。比如我们要比较图中的2和3,求它们最小亮的灯数,也就是题意要求的n,可以依次枚举。如果只把2的0~3号灯开掉,其它灯都关闭,这时候它会产生一个新的“状态”,对应着一个十进制数。再只把3的0~3号灯打开,其他灯关闭,它也会产生一个新的“状态”,对应一个十进制数。
因为每个“状态”是唯一的,如果这两个状态对应的十进制数相同,说明2和3只开0~3号灯后的情况是完全相同的,这样就区分不开了。不符合题意,重新枚举情况。
如果去掉以后他们各自的十进制数不同,这时候就是满足题意的一种状态,“把0~3号灯打开”这个状态就是满足题意的。这时候根据这个状态计算出亮着的灯,即1的个数即可。
当然,我上面举的那个例子不符合条件。如果只打开0~3号灯,2和3就完全一样了。注:原本不亮的地方不能开。其实这个地方应该表述成关上4~6的灯比较合适。
③如何知道A状态在B状态下的状态?只要用B & A即可。
例如,只关0号灯的状态是 0 1 1 1 1 1 1,如果要求5在此状态下长什么样的,只要用1 1 0 1 0 1 0(5原本的状态) & 0 1 1 1 1 1 1,得到0 1 0 1 0 1 0 。
以此类推,把所有状态都过一遍,不断更新最小亮灯数,最后输出。
感觉还是表述地不太清楚。大家有什么问题尽管问~
最后再膜拜一下想出这个办法的人,虽然这个算法不一定是最快的,但是这种方法真是让人回味无穷啊。。或许ACM的魅力就在这里。。
晚上再理解一下子集生成法的做法。
详情见代码~
#include <cstdio>
#include <cstring>
#include <set>
#include <algorithm>
using namespace std;
int bin[110];
int main()
{
//freopen("input.txt", "r", stdin);
int T, n, light, bound, mini, bit, temp, t;
int flag;
scanf("%d", &T);
set<int> st;
while (T--)
{
scanf("%d%d", &light, &n);
memset(bin, 0, sizeof(bin));
bound = (1 << light); //bound,即“状态”的总个数,每盏灯对应两个状态,有几盏灯就有2^n。
mini = 20;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < light; j++)
{
scanf("%d", &temp);
bin[i] += (temp << j); //把每一盏灯变成一个对应的十进制数,存起来。
}
}
for (int i = 0; i < bound; i++)
{
st.clear();
for (int j = 0; j < n; j++)
{
t = i & bin[j];
if (st.count(t)) //判断这个状态是否已经存在
break;
else
{
flag = j;
st.insert(t);
}
}
if (flag == n - 1) //说明都不重复,满足题意,这时候判断亮灯数是否为最小。
{
int ans = 0;
bit = i;
while (bit > 0)
{
if (bit & 1)
ans++;
bit >>= 1; //相当于 b /= 2;
}
mini = min(ans, mini);
}
}
printf("%d\n", mini);
}
return 0;
}