【动态规划】回路计数

1、题目描述

蓝桥学院由 21​​​ 栋教学楼组成,教学楼编号 11​​到 21​​。对于两栋教学楼 a​​ 和 b​,当 a​ 和 b​ 互质时,a 和 b 之间有一条走廊直接相连,两个方向皆可通行,否则没有直接连接的走廊。

小蓝现在在第一栋教学楼,他想要访问每栋教学楼正好一次,最终回到第一栋教学楼(即走一条哈密尔顿回路),请问他有多少种不同的访问方案?

两个访问方案不同是指存在某个 i,小蓝在两个访问方法中访问完教学楼 i 后访问了不同的教学楼。

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

答案提交

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

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 128M

题目来源:精选项目课程_IT热门课程_蓝桥云课课程 - 蓝桥云课


2、题目分析

考点:状态DP

例题:DP-状态压缩DP(解释+典例)

1、二进制形式表示状态

        二进制中每一位的1或者0可以表示物体存在或者不存在,而整个二进制的数值可以表示一共多少种状态。

2、位运算

&(按位与)

位运算
符号名称运算律作用
&按位与

1&1=1

1&0=0

0&1=0

0&0=0

|按位或

0|0=0

1|1=1

1|0=1

0|1=1

^异或运算

1^0=1

0^1=1

1^1=0

0^0=0

<<左移运算

5<<2  ==20

正数左边第一位补0

负数左边第一位补1

>>右移运算

5>>2

正数左边第一位补0,

负数左边第一位补1

~取反运算~1  == 0
>>>无符号右移1>>>1右移是高位补0

总结:

  • <<、>>与&运算结合使用可以确认二进制数某位是否为1
int num = 0000 0010;
//判断num的第二位数是否为1
num&(1<<1)
  • <<、>>与|运算结合使用可以确认二进制数某位是否为0

3、辗转相除法[3]

举例说明:假如需要求 100 和 18 这两个正整数的最大公约数

100 / 18 = 5 (余 10)
18 / 10= 1 (余 8)
10 / 8 = 1 (余 2)
8 / 2 = 4 (余 0)
至此,最大公约数为 2。

为什么辗转相除就求出了最大公约数?


  为什么上述辗转相除就求出了最大公约数?我们结合例子,简述一下原理。
  如果我们要求 8251 与 6105 的最大公约数(我们设该最大公约数是 x)的话,假设 8251 是这个数 x 的 a 倍,再假设 6105 是 x 的 b 倍,那么 2146 = 8251 - 6105,是 x 的 (a - b) 倍,也是 x 的倍数。而无论这几个数如何加减,甚至相乘,都还是最大公约数的倍数。
  我们就可以把求 8251 与 6105 的最大公约数简化成求 2146 和 6105 的最大公约数,再把求 2146 与 6105 的最大公约数简化为求 3959 ( = 6105 - 2146) 与 2146 的最大公约数。如此相减往复几次后,会发现两个数变相等了,这个数就是原来两个数的最大公约数。
 

4、状态转移方程的建立

        dp[ i ][ j ] 表示状态 i 以 j+1 号学院 为终点有多少种方案?

        ① i 表示当前的状态,一共21位数字,数字第w位为1表示当前路径中已经包含了第w+1栋教学楼;数字第w位为0表示当前路径中已经不包含第w+1栋教学楼;

        ② j 表示当前状态是以第 j+1 栋楼为终点

        ③假设Z_{21}Z_{20}...Z_2 Z_1为当前状态,也就是i的值。W_j+1表示第j栋教学楼,Router[i][j] == 1表示从第 i+1 栋楼到第 j+1 栋楼之间有直达路径。如果Z_1==1Z_k==0Router[Z_1][k] == 1,那么 dp[ Z_{21}Z_{20}...Z_2 Z_1  + 1<<k][k] += dp[Z_{21}Z_{20}...Z_2 Z_1][j]。

5、代码 

public class _回路计数 {
	static long dp[][] = new long[1<<21][25];
	static int route[][] = new int[25][25];

	//计算a,b是否互为质数,如果是,返回1;否则返回最大公因数
	static int gcd(int a, int b){
		return b==0?a:gcd(b, a%b);
	}

	public static void main(String[] args) {
		//初始化router数组
		for(int i=1;i<22;i++) 
			for(int j=1;j<22;j++) {
				if(gcd(i, j) == 1) 
					route[i-1][j-1] = route[j-1][i-1] = 1;
				else
					route[i-1][j-1] = route[j-1][i-1] = 0;

				}
		//初始化dp数组
		dp[1][0] = 1;
		
		//i表示状态,从1到100000000000000000000
		for(int i=1;i< (1<<21);i++) {
			//j表示第j+1栋教学楼
			for(int j=0;j<21;j++) {
				
				//判断当前状态是否含有第j+1栋教学楼
				//如果当前状态没有包含第j+1栋教学楼,执行代码
				if(( i>>j &1) == 0) 
					continue;
				
				//如果当前状态包含第j+1栋教学楼,执行代码
				for(int k=0;k<21;k++) {
					
					//如果当前状态包含了第k+1栋教学楼,或者第j+1栋教学楼到第k+1栋教学楼之间没有直达路径
					if( (i>>k&1) ==1 || route[j][k] == 0) 
						continue;
					
					dp[i+(1<<k)][k] += dp[i][j];
				}
			}
		}
		
		
		//计算结果
		//因为结果超过了int范围,所以使用long类型
		long ans = 0;
		
		//dp[(1<<21)-1][i]表示以第i+1栋楼为终点且遍历完所有的楼一遍的方案数
		//因为1和任何数互为质数,所以不用担心1和i+1之间没有路径
		for(int i=0;i<21;i++)
			ans += dp[(1<<21)-1][i];
		
		System.out.println(ans);
	}

}

 

6、注意事项

  • 直接把这个代码提交到OJ会超时,但是使用C|C++语言写的不会超时;
  • 只要直接输入结果就行,不懂把上述代码提交到OJ
public class Main{
    public static void main(String[] args){

        //初始化long类型的数据需要在末尾加上L
        long ans = 881012367360L;
        System.out.println(ans);
    }
}

参考文献

[1] 蓝桥杯.回路计数(状压DP)

[2] 位运算

[3] 辗转相除法

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值