1、题目描述
蓝桥学院由 21 栋教学楼组成,教学楼编号 11到 21。对于两栋教学楼 a 和 b,当 a 和 b 互质时,a 和 b 之间有一条走廊直接相连,两个方向皆可通行,否则没有直接连接的走廊。
小蓝现在在第一栋教学楼,他想要访问每栋教学楼正好一次,最终回到第一栋教学楼(即走一条哈密尔顿回路),请问他有多少种不同的访问方案?
两个访问方案不同是指存在某个 i,小蓝在两个访问方法中访问完教学楼 i 后访问了不同的教学楼。
提示:建议使用计算机编程解决问题。
答案提交
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
运行限制
- 最大运行时间:1s
- 最大运行内存: 128M
题目来源:精选项目课程_IT热门课程_蓝桥云课课程 - 蓝桥云课
2、题目分析
考点:状态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 栋楼为终点
③假设为当前状态,也就是i的值。表示第j栋教学楼,表示从第 i+1 栋楼到第 j+1 栋楼之间有直达路径。如果,且,那么 dp[ + 1<<k][k] += dp[][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);
}
}
参考文献