CF11D A Simple Task(状压DP)

传送门

题意:给定一个简单图,输出其中的简单环的数目(简单环是不包含重复顶点、重复边的环)

点数\(1<=N<=19\),边数\(M>=1\).保证不存在自环和重边.

\(f[S][i]\)表示:在点集S中,我们当前遍历到了i点,找到的简单环的数目.

第一维S状态压缩,用二进制来枚举点(0表示该点还没走过,1表示该点已经走过.)

因为点数最大不超过19,所以\(S<=2^{19}=524288\)

设j表示下一个(我们要访问的)点,也就是与当前点i相连接,但不属于点集S的点.于是得到状态转移方程:

\(f[S|(1<<j)][j]+=f[S][i]\)

而当j访问到点集S时,如果j回到了起点,则找到了环(之前肯定没有找到环,因为之前状态转移要求j不在点集S中,而起点就是在点集S中)

总结一下上述:如果j不在点集S中,就状态转移(方程在上方);如果j在点集S中,就判断j是不是回到了起点,如果j是回到了起点,则说明找到了环,ans累加简单环的数目\(f[S][i]\).

还有一些细节,直接结合代码来讲.

int n,m,Map[20][20];
long long ans,f[600005][20];
int lowbit(int x){
    for(int i=0;i<n;i++)
		if(x&(1<<i))return i;
}
int main(){
    n=read();m=read();
    for(int i=1;i<=m;i++){
		int a=read()-1,b=read()-1;
		Map[a][b]=1;Map[b][a]=1;
    }
//因为我们二进制状态压缩,为了方便处理,
//把点的编号[1,n]移到[0,n-1]中来.
//直接用二维邻接表标记a,b两点间是否有边.
    for(int i=0;i<n;i++)
		f[1<<i][i]=1;
//初始化:
//1<<i表示点集S中只有一个点i,此时又遍历到了点i
//显然就是存在了一个环.
//我们只能从当前不为0的状态转移到其它状态.
    for(int S=1;S<=(1<<n);S++)//枚举点集
		for(int i=0;i<n;i++){//枚举点
	    	if(!f[S][i])continue;
//我们只能从当前不为0的状态转移到其它状态.
	    	int p=lowbit(S);
//p是点集S(二进制)中最低非零位的位置,
//假设以该点为出发点,每次访问比它大的点作为下一点:
	    	for(int j=p;j<n;j++){
			if(!Map[i][j])continue;
//S&(1<<j)表示j回到了点集S中:
				if(S&(1<<j)){
		    		if(j==p)ans+=f[S][i];
//再判断是不是出发点,如果是的话,ans累加.
				}
				else f[S|(1<<j)][j]+=f[S][i];
//如果j没有回到点集S,满足状态转移条件.
	    }
	}
    printf("%lld\n",(ans-m)/2);
//因为我们是在无向图中找简单环,所以一定会出现:
//两个点一条边构成了一个非法环,即1---->2----->1
//这里会产生m个非法环,所以ans要先减去边数m,
//合法环会正向和反向计算两遍,所以还要除以2
    return 0;
}

posted on 2019-01-26 16:10  PPXppx  阅读(88)  评论(0编辑  收藏  举报