【备战蓝桥杯】9.回路计数——状压dp与图论

838 阅读2分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

姥姥的,这几天刷蓝桥杯的真题越刷越自闭,都快被整的怀疑人生了。现如今怎么蓝桥杯都这么难了!今天又试着做一道填空压轴题,在天降神灵(百度)的帮助下,终于理解了这道题,现就写下我自己的理解。

题目描述

蓝桥学院由 21 栋教学楼组成,教学楼编号 1 到 21。

对于两栋教学楼 a 和 b,当 a 和 b 互质时,a 和 b 之间有一条走廊直接相连,两个方向皆可通行,否则没有直接连接的走廊。

小蓝现在在第一栋教学楼,他想要访问每栋教学楼正好一次,最终回到第一栋教学楼(即走一条哈密尔顿回路),

请问他有多少种不同的访问方案?两个访问方案不同是指存在某个 i,小蓝在两个访问方法中访问完教学楼 i 后访问了不同的教学楼。

提示:建议使用计算机编程解决问题。

答案提交

这是一道结果填空的题,你只需要算出结果后提交即可。 本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

思路详解

这题乍一看,这不明显一个图给你摆这了吗!二话不多说先建图:

vector<int> graph[22];
...
    for(int i=1;i<=21;i++){
        for(int j=i+1;j<=21;j++){
            if(gcd(j,i)==1){
                graph[i].pb(j);
                graph[j].pb(i);
            }
        }
    }

其中有个知识点是互质怎么求?互质指的是两个数的最小公约数为1.典型的gcd,辗转相除法即可:

int gcd(int a,int b){
	if(a<b)return gcd(b,a);
	if(b==0)return a;
	return gcd(b,a%b);
}

(加上a<b的判断是为了保险)

然后,有些同学可能就杵这了,放在键盘上的手不知所措(这其中就包括我呜呜呜)。一般的图论,求解的往往是最短路径、最大流之类的最值问题,往往有一个最优解;但这道题求的是方案数,这样的话什么dfs呀,dijkstra呀,dicnic呀全部木大,而是又要祭出——状压dp大法。

为什么想到状压?因为只有21个点,每个点只经过一遍,从而一个21位的二进制数即可表示全部状态。但是有了状态,我们要怎么转移?知道了一个状态i,怎么把这个状态与其他状态建立联系?我们考虑第二维,j,是最后到达的点。

从而,我们的dp是这样的:dp(i,j)中的i是走过的点的状态,j是到达的最后一个点,dp值是方案数。

那么如何转移状态?换句话说,dp(i,j)所代表的状态从何而来?其实很简单。j是最后一个点,那么是从那个点走到j点的呢?遍历所有的可能性,然后全部加起来即可。

上代码

#include<bits/stdc++.h>
#define pb push_back
#define pp pop_back
using namespace std;
using ll=long long;
vector<int> graph[22];
int gcd(int a,int b){
	if(a<b)return gcd(b,a);
	if(b==0)return a;
	return gcd(b,a%b);
}
ll dp[1<<21][22];
int main(){
	for(int i=1;i<=21;i++){
		for(int j=i+1;j<=21;j++){
			if(gcd(j,i)==1){
				graph[i].pb(j);
				graph[j].pb(i);
			}
		}
	}
	dp[1][1]=1;
	for(int i=1;i<=(1<<21)-1;i++){
		for(int j=1;j<=21;j++){
			if((i>>(j-1))&1){
				ll lasti=i^(1<<j-1);
				for(auto k:graph[j]){
					dp[i][j]+=dp[lasti][k];
				}
			}
		}
	}
	ll ans=0;
	for(int i=1;i<=21;i++){
		ans+=dp[(1<<21)-1][i];
	}
	cout<<ans;
}

总结

这次尝试了一些不一样的风格,要是一直按照以前的方式写题解怕是最后要无聊死。还是应该把写题解变得轻松一点才好呀捏。