很容易想到这是一个集合覆盖问题。
我已开始还脑抽了,用什么强连通分量= =,他只是能中止相邻的,又不是中止一个块,但是样例能过。。。只能说自己有时候真的都没静下心来认认真真想清楚,分析好自己算法的正确性,而是瞎想觉得差不多啦就开始写代码了。
WA后又想了一个错误的解法。自己应该先确定算法,然后在纸上证明正确性,然后纸上跑几个数据,再编码才好。这种瞎想出来的解法,更是要严谨,不能随便脑子里模拟几个简单的例子就觉得对了。
然后如果自己的解法的时间复杂度远小于题目的数据量,那么你还是掂量一下吧。
1<=N<=16
一看就是指数级的时间复杂度。那就跑不了枚举集合啦。
每个点及其它的领点都是一个集合,多个集合的并集可能会覆盖所有点,而且可能覆盖很多次。那么该怎么动态的安排这些集合,让他们覆盖的次数最多呢?
动态规划。
如何选取集合是个问题,那我们就需要枚举集合的集合。下面将小集合叫元素,小集合的集合叫集合。
枚举集S合,再枚举集合的子集S0,如果S0能覆盖所有点,那么一种可行的方案就是让S0瘫痪一个服务,让S^S0去尽量瘫痪最多的服务。S0与S^S0都是S的子集,所以从小到大枚举集合S,然后取方案的最优解就好了,如果没有方案,那就是0。
dp[S]代表集合S最多能瘫痪几个服务。
状态转移方程为dp[S]=max(dp[S-S0]|S0是S的子集,且S0可以覆盖所有点)+1。dp[S]初始化为0,即无方案的情况。
预处理出所有情况是否能覆盖所有点,以O(1)判断。
大白书上的代码真的经典。特别是枚举子集那段太6。
代码
#include<bits/stdc++.h>
#define maxn 20
#define maxs (1<<16)
using namespace std;
int N;
int P[maxn];
int dp[maxs];
int cover[maxs];
int kase;
int CUL(int S)
{
int ans=0;
for(int i=0;i<N;i++)
if(S&(1<<i))
ans|=P[i];
return ans;
}
int main()
{
while(scanf("%d",&N)==1&&N)
{
for(int i=0;i<N;i++)
{
P[i]=0;
P[i]|=1<<i;
int m,v;
scanf("%d",&m);
for(int j=0;j<m;j++)
{
scanf("%d",&v);
P[i]|=1<<v;
}
}
int ALL=(1<<N)-1;
for(int S=0;S<(1<<N);S++)
{
cover[S]=CUL(S);
dp[S]=0;
for(int S0=S;S0;S0=(S0-1)&S)
if(cover[S0]==ALL) dp[S]=max(dp[S],dp[S^S0]+1);
}
printf("Case %d: %d\n",++kase,dp[(1<<N)-1]);
}
return 0;
}