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

题目:点我


题目大意:(黑客的攻击)假设你是一个黑客,侵入了了一个有着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);




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值