您的位置:首页 > 其它

uva 11825 黑客的攻击 Hackers' Crackdown 集合dp+我的优化 非常好的好题

2016-02-05 16:25 309 查看
题目:点我

题目大意:(黑客的攻击)假设你是一个黑客,侵入了了一个有着n台计算机(编号为0,1,…,n-1)的网络。一共有n种服务,每台计算机都运行着所有的服务。对于每台计算机,你都可以选择一项服务,终止这台计算机和所有与它相邻计算机的该项服务(如果其中一些服务已经停止,则这些服务继续处于停止状态)。你的目标是让尽量多的服务器完全瘫痪(即:没有任何计算机运行该项服务)  

输入格式:输入包含多组数据。每组数据的第一行为整数n(1<=n<=16);以下n行每行描述一台计算机的相邻计算机,其中第一个数m为相邻计算机个数,接下来的m个整数位这些计算机的编号。输入结束标志为n=0。

  输出格式:对于每组数据,输出完全瘫痪的服务器的最大数量。

几天以前看这个题目,觉得特别难,因为n很小,n<=16,所以很容易想到集合dp,可是就算想到了,当时也觉得非常难表示,因为觉得既要表示各种服务是否工作的集合,又要表示每台计算机切断哪种服务的集合。觉得非常难。

假设不用集合dp,那就更难,不仅是更难,简直是灾难,状态都无法表示。

我最近想问题,总是老想些过于复杂的部分,导致很多问题都无法思考出结果,比如这个题目,我总想着一个计算机选择一种服务后,有16种选择,还要遍历与他相邻的计算机,用动态规划去解决一个有复杂结构的图问题,怎么样想这个题能够解决都是令人无法相信的。

我发现决策最多只有n种,所以弄出了n个泛化物品(n个选择用n个物品表示),每个物品是一个集合,表示哪些计算机与选中的计算机相领,

然后把选中的计算机也加进去。

然后去枚举,哪些选中的组合能够完全停止一种服务。我们不必关心是哪一种,我们只关心是否能完全停止一种。

怎么去枚举?有事用int表示集合!表示选中了哪些为中心计算机。

最后开始状态转移,从0开始到(1<<n)-1 ,dp[x]表示状态x(切断了某些为中心的计算机后)能够完全停止的最多服务。

最后你会发现这样做会超时,因为泛化物品(指的是组合,能够完全停止一种服务的组合)实在数目太多了,后来想了个办法,把没用的物品(如果没有,也不影响最优解)都给他去了,时间省了不少。(newS()函数)

/**==========================================
*   This is a solution for ACM/ICPC problem
*
*   @source:uva 11825 Hackers' Crackdown
*   @type:  dp
*   @author: wust_ysk
*   @blog:  http://blog.csdn.net/yskyskyer123 *   @email: 2530094312@qq.com
*===========================================*/
#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#define ysk(x)  (1<<(x))
using namespace std;
typedef long long ll;
const int INF =0x3f3f3f3f;
vector<int>things;
int n,m,ed;
int a[18];
int dp[ysk(16)+5];
bool vis[ysk(16)+5];

bool newS(int s)
{
for(int i=0;i<n;i++) if(s&ysk(i))
{
int nst=s^ysk(i);
if(vis[nst])  return false;
}
return true;
}

void virtual_things()
{
things.clear();
memset(vis,0,sizeof vis);
for(int s=1;s<=ed;s++)
{
int tot=0;
for(int j=0;j<n;j++)  if(s&ysk(j))
{
tot|=a[j];
}
if(tot==ed)
{
if(newS(s)) things.push_back(s);
vis[s]=1;
}
}
}

int main()
{
int x,kase=0;
while(~scanf("%d",&n)&&n)
{
for(int i=0;i<n;i++)
{
int st=ysk(i);
scanf("%d",&m);
for(int j=0;j<m;j++)
{
scanf("%d",&x);
st|=ysk(x);
}
a[i]=st;
}
ed=ysk(n)-1;

virtual_things();
memset(dp,0, (ed+1)*sizeof dp[0]);
for(int st=0;st<ed;st++)
{
for(int i=0;i<things.size();i++)
{
int add=things[i];
if(st&add)  continue;
int nex=st|add;
dp[nex]=max(dp[nex],dp[st]+1);
}
}
printf("Case %d: %d\n",++kase,dp[ed]);

}

return 0;
}
/*
3
2 1 2
2 0 2
2 0 1
4
1 1
1 0
1 3
1 2

*/


这个题目确实觉得挺好,对于我这种小白,做这一题的过程简直是一波三折,做完令人回味无穷。

如果对于状态S,枚举S的子集进行动态规划(与我的不同)是不会超时的,时间复杂度O(3^n);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: