UVa 11825 (状压DP) Hackers' Crackdown
这是我做状压DP的第一道题,状压里面都是用位运算来完成的,只要耐下心来弄明白每次位运算的含义,还是容易理解的。
题意:
有编号为0~n-1的n台服务器,每台都运行着n中服务,每台服务器还和若干台其他服务器相连。对于每台服务器,你可以选择停止该台以及与这台服务器相连的服务器的一项服务。如果一台服务器的所有服务都被停止,则这台服务器瘫痪。问最多能使多少台服务器瘫痪
转化为数学模型(题目是如何抽象成这种数学模型的也要好好想想):
把n个集合尽可能多的分成若干组,使得每组所有集合的并集为全集。这里集合Pi表示服务器i以及与其相邻服务器的集合
算法:
数组P[i]表示服务器i以及与其相邻服务器的集合的二进制表示
枚举所有集合可能的组合情况,一共有2n种,计算其并集保存在cover中
f[S]表示子集S最多可以分成多少组
状态转移方程:
枚举S的子集S0
f[S] = max{f[S], f[S - S0] + 1 | 这里S0是S子集而且cover[S0]为全集}
这里再说一下枚举S0用代码实现的技巧:
在二进制中S0的1的个数肯定比S要少,所以从数值大小上来看S0 < S
我们先令S0 = S - 1
那么 S & (S0 - 1)就是S的子集了
因为上面的表达式满足两个条件:
- S0 < S
- S0的1的个数比S要少
S ^ S0的含义:
S ^ S0代表以S为全集S0的补集,为什么用异或运算实现呢?自己动手模拟一下就清楚了,嘿嘿
好了,写到这里,这道题就分析的很透彻了。看来位运算还是蛮有意思的
1 //#define LOCAL 2 #include <iostream> 3 #include <cstdio> 4 #include <cstring> 5 #include <algorithm> 6 using namespace std; 7 8 const int maxn = (1 << 16) + 10; 9 int cover[maxn], p[20], f[maxn]; 10 11 int main(void) 12 { 13 #ifdef LOCAL 14 freopen("11825in.txt", "r", stdin); 15 #endif 16 17 int n, kase = 0; 18 while(scanf("%d", &n) == 1 && n) 19 { 20 memset(f, 0, sizeof(f)); 21 for(int i = 0; i < n; ++i) 22 { 23 int m, x; 24 scanf("%d", &m); 25 p[i] = (1 << i); 26 while(m--) 27 { 28 scanf("%d", &x); 29 p[i] |= (1 << x); 30 } 31 } 32 33 for(int i = 0; i < (1 << n); ++i) 34 { 35 cover[i] = 0; 36 for(int j = 0; j < n; ++j) 37 { 38 if(i & (1 << j)) 39 cover[i] |= p[j]; 40 } 41 } 42 43 f[0] = 0; 44 int all = (1 << n) - 1; 45 for(int s = 1; s < (1 << n); ++s) 46 { 47 f[s] = 0; 48 for(int s0 = s; s0; s0 = (s0 - 1) & s) 49 if(cover[s0] == all) 50 f[s] = max(f[s], f[s0 ^ s] + 1); 51 } 52 53 printf("Case %d: %d\n", ++kase, f[all]); 54 } 55 return 0; 56 }