好久木有写过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