您的位置:首页 > 其它

UVa 11825 Hackers’ Crackdown

2015-04-06 19:57 281 查看
参照大白书上面的解法,总共三个步骤,前两个步骤都较好理解。P[i]是用位表示的当选中i时,总共有0~n-1总共有多少个数字被覆盖。cover[S]则表示,当子集为S时,0~n-1中能够被覆盖的位数。若cover[S]的每位都为1,则说明子集S能对全集进行覆盖,当然可能子集S的子集就能做到这一点了。

关键的步骤是对状态转移方程的理解。书中的状态转移方程是f(S)=max{f(S0)|S0是S的子集,cover[S0]等于全集}+1,其实对于这个方程我一直感觉怪怪的,并不十分理解它的意思,虽然它最后也能AC。根据我自己的理解的转移方程应当是这样的,f(S)=max{f(S0)+f(S^S0)|S0是S的子集,cover[S0]等于全集}且必须保证cover[S]为全集时,f(S)至少为1,这样做是为了保证S为最小覆盖集时,f(S)的值为1。这样的解法,最后也是能AC的,而且我个人认为思路比书上的更清晰一些。

至于为啥for(int S0=S;S0;S0=(S0-1)&S)能遍历S的所有子集,严密的证明方法还没想到。但直观上来看,可以发现。例如S为10101,则S0依次为10101,10100,10001....我们完全可以将10101中间的0忽略,因此整个for循环就变为了111,110,101,100..这样逐个减一的过程。其实仔细想来也是,S0减一的过程,就是将最低的非0位置0,然后将比该位低的所有位都置1,经过与S相与之后,则之前的低位又变为了开始时候的样子,因此整个过程可以看做是一个递归循环的过程。(说得一点都不清楚....自己模拟一下应该就能理解)

#include <iostream>
#include <cstdio>
#include <algorithm>
#define MAX     16+5
#define MAXN    (1<<16)+5
using namespace std;

int N,Case=1;
int cover[MAXN],P[MAX],f[MAXN];

int main()
{
    //freopen("data.txt","r",stdin);
    while(cin>>N){
        if(!N) break;

        for(int i=0;i<N;++i){
            int m,x;
            cin>>m;
            P[i]=1<<i;
            while(m--){cin>>x;P[i]|=(1<<x);}
        }

        for(int S=0;S<(1<<N);++S){
            cover[S]=0;
            for(int i=0;i<N;++i){
                if(S&(1<<i)) cover[S]|=P[i];//获得子集S的覆盖
            }
        }

        f[0]=0;
        int ALL=(1<<N)-1;//ALL表示全集
        for(int S=1;S<(1<<N);++S){
            if(cover[S]==ALL){
                f[S]=1;
            }
            else{
                f[S]=0;
                continue;
            }
            for(int S0=S;S0;S0=(S0-1)&S)//枚举S的子集
                if(cover[S0]==ALL) f[S]=max(f[S],f[S0^S]+f[S0]);

        }
        cout<<"Case "<<Case++<<": "<<f[ALL]<<endl;
    }

    return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: