題意:假設你是一個黑客, 侵入了一個有着n臺計算機(編號爲0,1,…,n-1) 的網絡。 一共有n種服務,每臺計算機都運行着所有服務。對於每臺計算機,你都可以選擇一項服務, 終止這臺計算機和所有與它相鄰計算機的該項服務(如果其中一些服務已經停止, 則這些服務繼續處於停止狀態)。 你的目標是讓儘量多的服務器完全癱瘓(即: 沒有任何計算機運行該項服務)。
思路:這道題的話,要注意的地方是如果一個電腦被入侵了,那麼它的相鄰的電腦的相鄰的電腦也會是被入侵了。
p[i]表示的與i直接相鄰的一個狀態,沒太大直接意義,主要是爲了推出c的狀態,c[i]表示的是i狀態被選擇時,n個電腦被覆蓋的狀態。dp[i]表示i狀態被選擇,已經被入侵的最大服務數。用到枚舉子集,假設集合i爲j的一個子集,則(i-1)&j爲j的下一個子集,直至i爲0。其實就是一個狀態能夠達到全集,然後和另外一個狀態合併成一個新的狀態,然後就是dp取最大值。
AC代碼:
#include <stdio.h>
#include <string.h>
#include <string>
#include <algorithm>
#include <iostream>
#include <math.h>
typedef long long ll;
const int maxx=20;
const int inf=0x3f3f3f3f;
using namespace std;
int p[maxx];
int c[1<<maxx];
int dp[1<<maxx];
int main()
{
int n;
int k=1;
while(~scanf("%d",&n),n)
{
int a,b;
for(int i=0; i<n; i++)
{
scanf("%d",&a);
p[i]=1<<i;//左移,構造一個只有第i+1位是1的二進制數,爲了將0表示在第一位上,故這樣操作
while(a--)
{
scanf("%d",&b);
p[i]|=(1<<b);//與i直接相連的電腦的表示
}
}
c[0]=0;
for(int i=1; i<=(1<<n); i++)
{
c[i]=0;//i表示枚舉所有電腦組成的情況
for(int j=0; j<n; j++)
{
if(i&(1<<j))
c[i]|=p[j];//若此位上有電腦則將其所有相鄰的電腦及本身加入此集合
}
}
int ans=(1<<n)-1;//只有第n+1位沒有電腦,即所有n臺都被感染
for(int i=1; i<(1<<n); i++)
{
dp[i]=0;
for(int j=i; j; j=(j-1)&i)//子集枚舉,減少電腦數與原集合取交集
{
if(c[j]==ans)//每臺電腦都運行着所有的服務,題意要求每臺電腦可以選擇關一項服務,如果c[j]==all表示如果關掉(j在二進制下的表示)的電腦上的某服務,必有一項服務在所有電腦上都被關掉
dp[i]=max(dp[i],dp[i^j]+1);
}
}
printf("Case %d: %d\n",k++,dp[ans]);
}
return 0;
}