题目
蓝桥学院由 21 栋教学楼组成,教学楼编号 1 到 21。
对于两栋教学楼 a 和 b,当 a 和 b 互质时,a 和 b 之间有一条走廊直接相连,两个方向皆可通行,否则没有直接连接的走廊。
小蓝现在在第一栋教学楼,他想要访问每栋教学楼正好一次,最终回到第一栋教学楼(即走一条哈密尔顿回路),
请问他有多少种不同的访问方案?两个访问方案不同是指存在某个 i,小蓝在两个访问方法中访问完教学楼 i 后访问了不同的教学楼。
提示:建议使用计算机编程解决问题。
答案:881012367360
思路:状态压缩+记忆化搜索
记得一开始没多想,直接写了个深度搜索。如果是这样的话,时间复杂度是O(n21)。51090942171709440000种状态需要你去遍历,我直接吐了。写完一道题来着,最后发现跑了十多分钟一点反应没有。
首先这个题很明显用记忆化搜索,但是我当时用的String存状态,这样没法存储完整状态。因为你要去dp数组查找的一定是情况一样的,也就是21位数都要满足。这里就需要用到状态压缩了。
状态压缩是使用某种方法,简明扼要地以最小代价来表示某种状态,通常是用一串01数字(二进制数)来表示各个点的状态。这就要求使用状态压缩的对象的点的状态必须只有两种,0 或 1;
https://blog.csdn.net/weixin_45697774/article/details/104874248
我们用1代表走过了这个教学楼,那么是初始状态:10000....0000
,如果我们第二次去3号楼,那么状态就变成:10100000.....0000
。那么用if去判断走没走过一个教学楼呢?我们设状态为state。如果state>>i&1
为1,那么这个第21-i
位就是1,也就代表着这个教学楼走过了。
for(int i=19;i>=0;i--) 这是最终代码的一部分
{
if((state>>i&1)==1||!matrix[21-pre][21-i])
continue;
}
其次还有就是如何维护dp数组呢?我们用ans记录最终的答案数,在遍历的时候,我们只需要用个t记住在没dfs一个状态的值,最后dfs这个状态,那么ans可能变了,也可能没变。但是ans-t
就是dp[state][X]
的值。
如果没懂就配合代码食用…
public class new_class {
static long ans=0;
static long[][] dp=new long[1<<22][22];
static boolean[][] matrix=new boolean[22][22];
public static void main(String[] args) {
for(int i=1;i<22;i++)
for(int j=i+1;j<22;j++)
if(gcd(i,j)==1)
matrix[i][j]=matrix[j][i]=true;
for(int i=0;i<1<<22;i++)
Arrays.fill(dp[i], -1);
dfs(1<<20,20); //第二个参数是20代表着,我们这个状态的上一次操作是21-20=1。
//为什么要这样设置呢,因为dfs里面用起来就方便了。有点绕,理解就好。
System.out.println(ans);
}
static void dfs(int state,int pre)
{
if(state==(1<<21)-1) //如果所有的点都遍历,那么ans++。
//不用判断最后一个教学楼是否能到1号教学楼,因为1号教学楼其实和所有的教学楼都是通的。
{
ans++;
return;
}
Queue<Integer> Q =new LinkedList<Integer>();
for(int i=19;i>=0;i--)
{
if((state>>i&1)==1||!matrix[21-pre][21-i])
continue;
Q.add(i);
}
if(Q.size()==0) return;
long t;
while(Q.size()!=0)
{
if(dp[state][Q.peek()]!=-1)
{
ans+=dp[state][Q.peek()];
Q.poll();
continue;
}
t=ans;
dfs(state+(1<<Q.peek()), Q.peek());
dp[state][Q.poll()]=ans-t; //通过上面的dfs,差值就是这个状态对应的值
}
}
static int gcd(int a,int b)
{
if(a%b==0)
return b;
return gcd(b, a%b);
}
}
程序耗时为:1.5s