ACM
题目地址:11825 - Hackers' Crackdown
题意:
有一个由编号0~n-1的n台计算机组成的网络,一共有n种服务,每台计算机上都运行着全部服务,对于每台计算机,你可以选择停止一项服务,这个行为会导致与这台计算机和与他相连的其他计算机上的这项服务都停止(原来已经停止的继续保持停止状态)。求最多能使多少个服务瘫痪(即没有任何一台计算机在运行这项服务)。
分析:
题目说白了,就是:
把n个集合p[i],0<=i<n
分成尽量多组,使得每组中各个集合的并集为全集。
利用状态压缩,记录每个节点运行的服务。由于数据大小就16所以直接可以用int范围数字表示一个集合。
然后预处理下cover,处理16个节点组成的各个集合会带来的挺服务效果。
然后dp,如果cover[S0] == all (all全为1) 那么是S^S0 的部分也有可能终止服务 ,dp[S] = max(dp[S], dp[S^S0]+1)
。
参考了凌乱的心巨巨的题解,嘛,是为了了解枚举子集做的题目。
枚举子集的模板:
// 对于集合S
for (int S0 = S; S0; S0 = S&(S0 - 1)) // 枚举S0为子集
...
原理:
S&(S0 - 1)
实际上是把S中的0全部忽略,并不断减1的结果。
代码:
/* * Author: illuz <iilluzen[at]gmail.com> * File: 11825.cpp * Create Date: 2014-06-27 20:43:48 * Descripton: sub set/ dp/ numeric */ #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N = 16; int n, m, t, mask[N], cover[1<<N], dp[1<<N], tot; int main() { int cas = 0; while (~scanf("%d", &n) && n) { // input for (int i = 0; i < n; i++) { scanf("%d", &m); mask[i] = (1 << i); while (m--) { scanf("%d", &t); mask[i] |= (1 << t); } } // get the union set of cover for (int S = 0; S < (1 << n); S++) { cover[S] = 0; for (int i = 0; i < n; i++) { if (S & (1 << i)) { cover[S] |= mask[i]; } } } // dp dp[0] = 0; tot = (1 << n) - 1; for (int S = 1; S < (1 << n); S++) { dp[S] = 0; for (int S0 = S; S0; S0 = (S0 - 1)&S) { if (cover[S0] == tot) { dp[S] = max(dp[S], dp[S^S0] + 1); } } } printf("Case %d: %d\n", ++cas, dp[tot]); } return 0; }