第12届蓝桥杯省赛A组C++回路计数(统计哈密尔顿回路个数,状压dp,记忆化搜索,超详解)
2022/1/5 22:04:21
本文主要是介绍第12届蓝桥杯省赛A组C++回路计数(统计哈密尔顿回路个数,状压dp,记忆化搜索,超详解),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
答案:881012367360
题意:给一个无向图,求哈密顿回路数
分析:暴力思路就是数搜索路径,时间复杂度是O(n^n)级别的,超过15就跑不动了。所以需要优化。因为是计数问题,不是优化问题,带剪枝的回溯法也不适用。
于是想到了记忆化搜索(类dp),看到21能想到状压dp,正好2的21次int能存下,先尝试使用 dp [2 ^ 21]一维数组,转换成2进制后1代表访问过,0代表没访问过,可以表示所有点是否被访问的状态,dp[state]存有多少种方式可以形成状态state。然后发现状态转移方程无法表示,比如状态11101到状态11111,如果路径为1-3-5-4,则不能和2连通,而路径1-3-4-5就可以接着访问2,只有dp[11101]中的一部分可以为dp[11111]所用,因此dp[11111]不能用dp[11101]推出。
于是可以看出该问题不仅和是否访问有关,还和访问顺序有关,如果只把是否访问作为状态,则状态的粒度过大,无法利用是否联通的信息。我们需要重新设置含访问顺序的状态。但是如果把所有顺序存下,则dp会失效,因为dp的思想本来就是将暴力搜索树中的部分状态合并,如果所有的顺序都存下,和暴力搜索复杂度没有区别。
于是我们继续观察,根据题意互质的结点才相连,在状态10111下路径1-2-3-5和路径1-3-2-5虽然顺序不同,但是最后的5号点都是能和4相连,导出状态11111;还是状态10111,1-3-5-2和路径1-5-3-2都不能和4相连,不能导出状态11111,我们只需要再加一个最末位置,就可以分割开这两种情况,同时把1-2-3-5与1-3-2-5合并为一个状态 dp[10111][5] , 1-3-5-2与1-5-3-2合并为一个状态 dp[10111][2] ,而dp[11111]中的dp[11111][4]=dp[10111][5]+dp[10111][3],于是顺其自然的也推出了状态转移方程,在本例中这个式子的意思是,最后访问4的状态数量等于倒数第2个访问3的路径数加上和倒数第二个访问5的路径数。
总结:
1.看到N<=21左右,想到状态压缩
2.有限制的选择计数问题,考虑(类)dp和记忆化搜索
3.带着实例去分析问题往往更容易发现规律,更高效
4.状态的设置和状态转移方程紧密相连,往往设置状态要从一维开始,如果一维状态无法推出转移方程再考虑二维,高维。
5.记忆化搜索的核心是合并暴力搜索树上的结点,因此一定要朝合并状态方向去分析,写代码时也一定要用到存起来的状态。
#include <bits/stdc++.h> const int maxn=21; const int maxs=1<<maxn; typedef long long ll; using namespace std; int N; ll dp[maxs+100][maxn];//存状态 int g[maxn+5][maxn+5];//存图 ll dfs(int state,int pos){ if(dp[state][pos]!=-1)return dp[state][pos];//一定要用到存储的状态!!! ll res=0; for(int i=1;i<N;i++){ if(((state>>i)&1)==0)continue; if(g[i][pos]==1){ dp[state-(1<<pos)][i]=dfs(state-(1<<pos),i); res+=dp[state-(1<<pos)][i];//不要直接用dp[state][pos]加因为初值是-1!! } } return dp[state][pos]=res; } int main() { memset(dp,-1,sizeof dp); cin>>N; while(1); for(int i=1;i<=N;i++){ for(int j=i;j<=N;j++){ if(__gcd(i,j)==1)g[i-1][j-1]=1,g[j-1][i-1]=1;//1到N映射为0到N-1 } } for(int i=1;i<N;i++){//初始化 dp[(1<<i)+1][i]=1; } ll sum=0; for(int i=1;i<N;i++){ dp[(1<<N)-1][i]=dfs((1<<N)-1,i); sum+=dp[(1<<N)-1][i]; cout<<dp[(1<<N)-1][i]<<endl; } cout<<sum<<endl; return 0; }
注意:dp数组要初始化为-1,这样可以判断state有没有更新过,如果更新过可以直接在函数开头return dp[state][pos]
同时记得不要直接dp[state][pos]+=x,因为初值是-1。
附两个有该题解的链接,可以参照着学习:
https://blog.csdn.net/weixin_46239370/article/details/105464885
中的https://blog.csdn.net/weixin_46239370/article/details/116805499
还要
https://blog.csdn.net/qq_36306833/article/details/121872050
这两个博客还有其他题的题解
这篇关于第12届蓝桥杯省赛A组C++回路计数(统计哈密尔顿回路个数,状压dp,记忆化搜索,超详解)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23增量更新怎么做?-icode9专业技术文章分享
- 2024-11-23压缩包加密方案有哪些?-icode9专业技术文章分享
- 2024-11-23用shell怎么写一个开机时自动同步远程仓库的代码?-icode9专业技术文章分享
- 2024-11-23webman可以同步自己的仓库吗?-icode9专业技术文章分享
- 2024-11-23在 Webman 中怎么判断是否有某命令进程正在运行?-icode9专业技术文章分享
- 2024-11-23如何重置new Swiper?-icode9专业技术文章分享
- 2024-11-23oss直传有什么好处?-icode9专业技术文章分享
- 2024-11-23如何将oss直传封装成一个组件在其他页面调用时都可以使用?-icode9专业技术文章分享
- 2024-11-23怎么使用laravel 11在代码里获取路由列表?-icode9专业技术文章分享
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享