题目:https://cn.vjudge.net/contest/171116#problem/C
N台电脑,现在有N种服务,现在你可以在每台电脑终止一项服务,他和他相邻的电脑都会被关闭,如果一项服务在所有电脑都没运行,该项服务成功被破坏,问最多能破坏几种服务。
把该题转化成数学模型。把一个集合,分割成几个子集,每个子集中挑选几个数,组成集合,能够并成全集。
4
1 1
1 0
1 3
1 2
如0 1相连接,2 3相连,把 0 2破坏相同服务,即可破坏该服务,1 3破坏另一种,总共2种服务被破坏。
思路:
这道题首先把每个节点能够到达的节点都找出来。首先找到该节点相邻的节点,然后将相邻节点的相邻节点更新到当前节点当中。这里可以用二分法。s[i]初始化为1<<i,表示第i个节点。然后将其相邻节点temp用s[i]|=1<<temp放入,最后sta[i]表示第i个节点能到达的所有点。
接下来是状态转移方程,f[s]=max(f[s],f[s-s0]+1).s0表示s的子集,且能到达所有节点,so能够破坏一个服务。s-s0表示s中剩下的节点。f[s]表示集合s破坏的最多服务数。用s0模拟s的每一个子集,s0=(s0-1)&s就能模拟每个子集。
#include<iostream>
#include<stdio.h>
#include<algorithm>
using namespace std;
#define maxn 1<<18
int sta[maxn],f[maxn];
int main()
{
int n,m,i,j;
int s[18], q = 1;
while (~scanf("%d", &n) && n != 0)
{
for (i = 0; i < n; i++)
{
s[i] = 1 << i;
scanf("%d", &m);
while(m--)
{
int temp;
scanf("%d", &temp);
s[i] |= (1 << temp);//初始化第i个相邻的节点
}
}
for (i = 0; i < (1 << n); i++)//sta[i]表示和i能够到达的所有相邻节点 dp[i]
{
sta[i] = 0;
for (j = 0; j < n; j++)
if (i&(1 << j))
sta[i] |= s[j];
}
f[0] = 0; int all = (1 << n) - 1;
for (int s = 1; s <= all; s++)//f[s]表示集合s能够破坏的最多服务数量
{
f[s] = 0;
for (int s0 = s; s0; s0 = (s0 - 1)&s)
{
if (sta[s0] == all)
f[s] = max(f[s], f[s^s0] + 1);//这里的解释是当S中的自己S0可以 //的时候,这相对于补集+1
}
}
printf("Case %d: %d\n", q++, f[all]);
}
return 0;
}