HDU 5823 color 状压DP

时空隧道


好久木有写过blog了….
感觉每次写DP的题解访问量都是最多的…..


题目大意:
你有⼀个 n 个点的⽆向图,点的标号从 0 到 n − 1。
众所周知,n 个点的集合有 2 n − 1 个⾮空⼦集。
对于每⼀个⾮空⼦集 S,定义 S 的合法染⾊是对 S 内的每个点染⼀种
颜⾊,使得 S 中不存在两个同⾊点间有边相连。
定义⼀个集合 S 的染⾊数 k 是 S 集合最⼩的合法染⾊的颜⾊数。
你的任务是求 n 个点的每⼀个⾮空⼦集的染⾊数。
(盗用zrt的翻译….>_<…..)


分析:
看到n<=18我那个开心….这不是妥妥的状压DP吗…再看一下问题….很激动呀,要求求出每个子集的ans也就是要遍历所有状态….这绝对是状压DP没错了….
然后一激动就GG了….我居然只枚举了比i少一个点的子集……(脑残的孩子只有慢慢来…)
状态转移其实很简单,f[i]=min(f[i],f[i^j]+1),j是i的子集并且j是一个独立集(就是说这些点没有边相连)大概就是说这些点都可以用一种颜色搞定….
首先遍历所有状态是2^n,枚举所有子集可是3^n(一个点有3种状态,属于当前集合但不属于子集,不属于当前集合,属于当前集合并且属于子集),有没有简单一点的枚举方式避免枚举无用状态?当然有了….
for(int j=i;j>0;j=(j-1)&i)
这很明显j是i的子集…..避免判断当前点集是否为i的子集….这样总复杂度为3^n….(j是否为独立集可以预处理的…..)
zrt的处理方法很机智:
因为这道题给边方式是邻接矩阵,所以就可以O(n)的判断了
感觉这道题收获还是很多的…..
还有一点很重要就是千万不要忘了位运算,位运算可以优化很多东西….(又想起了hhd出的回家种田的”水题”…..)


代码如下:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
//by NeighThorn
using namespace std;
const int maxn=18+5;
int n,M[maxn],independent[1<<18];
unsigned int f[1<<18];
char str[maxn];
inline unsigned int power(unsigned int a,int b){
    unsigned int ans=1;
    while(b){
        if(b&1)
            ans=ans*a;
        a=a*a,b>>=1;
    }
    return ans;
}
signed main(void){
//  freopen("color.in","r",stdin);
//  freopen("color.out","w",stdout);
    int cas;
    scanf("%d",&cas);
    while(cas--){
        scanf("%d",&n);
        for(int i=0;i<n;i++){
            scanf("%s",str);
            M[i]=0;
            for(int j=0;j<n;j++)
                if(str[j]=='1')
                    M[i]|=1<<j;
        }
        for(int i=1;i<1<<n;i++){
            independent[i]=1;
            for(int j=0;j<n;j++)
                if((i>>j)&1)
                    if(i&M[j]){
                        independent[i]=0;
                        break;
                    }
        }
        f[0]=0;
        for(int i=1;i<1<<n;i++){
            f[i]=n;
            for(int j=i;j>0;j=(j-1)&i)//怎么枚举子集很重要... 
                if(independent[j])
                    f[i]=min(f[i],f[i^j]+1);
        }
    //  for(int i=1;i<1<<n;i++)
    //      cout<<i<<" "<<f[i]<<endl;
        unsigned int ans=0;
        for(int i=1;i<1<<n;i++)
            ans+=f[i]*power((long long)233,i);
        cout<<ans<<endl;
    }
    return 0;   
}

by >_< NeighThorn

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值