题意:有一个工作网络,有n台计算机,总共有n个服务,先开始每个计算机都运行着这n项服务,现在有一个黑客他要入侵,他能停止每台计算机上的一项服务,那么和这台计算机相邻的的计算机的这项服务也会停止,如果一项服务在所有计算机中都停止了,那么这项服务就瘫痪了。问你最多他能瘫痪多少项服务。
思路:先开始这道题没想法,又是要全部停止才算瘫痪,又是网络的,后来一看数据范围,n是16,马上就想到状态压缩了,可是就感觉很麻烦,很难下手,模型抽象不出来。
看了一下书,也是瞬时顿悟了。其实这道题就是给你p0、p1、p2……pn-1这n个集合,问你最多能分多少组,使得每组的并集是全集。用d[ s ] 表示状态s最多能分多少组,那么d[ s ] = max( d[ s0 ] +1,s0为全集 )。所以在状态转移之前还要预处理一个cover数组,用来表示选的那几个集合的并集。这里枚举i的子集做状态转移的时候,还是有枚举方法的,千万不要j++这样枚举,具体见下面的代码:
for(int i = 1;i<=S;i++) { d[i]=0; for(int j = i;j;j = (j-1)&i) if(cover[j]==full) d[i] = max(d[i],d[i^j]+1); }
这里的j枚举的不是子状态,而是要变成i所需要拼上去的那部分。如果j要直接枚举子状态,那么需要增加一个特判,即j=0,因为for里面的终止条件是j>0,也就是说漏了个j=0,如果改成j>=0,由于j的转换方程,那么会陷入无限循环。上述代码的时间复杂度是O(3^n)。
又想起下午多校那一道了,之前就做过的话,就不会TLE了。。 = =
代码如下:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int MAXN = 1<<17; int p[MAXN]; int n; int cover[MAXN]; void get_cover() { int S = (1<<n)-1; cover[0]=0; for(int s=1;s<=S;s++) { cover[s] = 0; for(int i = 0;i<n;i++) { if(s&(1<<i)) cover[s] |= p[i]; } } } int d[MAXN]; int main() { int cas = 0; while(scanf("%d",&n)&&n) { for(int i = 0;i<n;i++) { int m; scanf("%d",&m); int a; p[i] = (1<<i); while(m--) { scanf("%d",&a); p[i] |= (1<<a); } } get_cover(); int S = (1<<n)-1; d[0]=0; int full = (1<<n)-1; for(int i = 1;i<=S;i++) { d[i]=0; for(int j = i;j;j = (j-1)&i) if(cover[j]==full) d[i] = max(d[i],d[i^j]+1); } printf("Case %d: %d\n",++cas,d[S]); } return 0; }