目录
一,九连环
许多人一定很熟悉九连环(如下图)
九个环被串在一起,操作规则如下:第一个(右边)环可以任意装卸,如果第k个环没有被卸掉,而第k个环前边(右边)的所有环都被卸掉,则第k+1个环(第k个环左边的环)可以任意装卸(如果存在的话)。
用0表示此换被卸掉,1表示此环没有被卸掉,则九连环的每个状态可以用一个长度为9的二进制串来表示,如:111111001经过一次操作可以变成111111000,也可以变成111111011,111111111经过一次操作可以变成111111110,也可以变成111111101。
这是我八分钟盲拧九连环的视频:
二,九连环所有步骤
九连环就是典型的递归
只需要up和down2个函数互相调用即可
对于初始状态111111111,要变成000000000,需要在main函数里面调用9次down函数
代码:
#include<iostream>
using namespace std;
int l[10];
void down(int n);
void up(int n)
{
if (l[n])return;
up(n - 1);
for (int i = n - 2; i > 0; i--)down(i);
cout << "上" << n << " ";
l[n] = 1;
}
void down(int n)
{
if (n*l[n] == 0)return;
up(n - 1);
for (int i = n - 2; i > 0; i--)down(i);
cout << "下" << n << " ";
l[n] = 0;
}
int main()
{
for (int i = 0; i < 10; i++)l[i] = 1;
for (int i = 9; i>0; i--)down(i);
return 0;
}
结果:
下1 下3 上1 下2 下1 下5 上1 上2 下1 上3 上1 下2 下1 下4 上1 上2 下1 下3 上1 下2 下1 下7 上1 上2 下1 上3 上1 下2 下1 上4 上1 上2 下1 下3 上1 下2 下1 上5 上1 上2 下1 上3 上1 下2 下1 下4 上1 上2 下1 下3 上1 下2 下1 下6 上1 上2 下1 上3 上1 下2 下1 上4 上1 上2 下1 下3 上1 下2 下1 下5 上1 上2 下1 上3 上1 下2 下1 下4 上1 上2 下1 下3 上1 下2 下1 下9 上1 上2 下1 上3 上1 下2 下1 上4 上1 上2 下1 下3 上1 下2 下1 上5 上1 上2 下1 上3 上1 下2 下1 下4 上1 上2 下1 下3 上1 下2 下1 上6 上1 上2 下1 上3 上1 下2 下1 上4 上1 上2 下1 下3 上1 下2 下1 下5 上1 上2 下1 上3 上1 下2 下1 下4 上1 上2 下1 下3 上1 下2 下1 上7 上1 上2 下1 上3 上1 下2 下1 上4 上1 上2 下1 下3 上1 下2 下1 上5 上1 上2 下1 上3 上1 下2 下1 下4 上1 上2 下1 下3 上1 下2 下1 下6 上1 上2 下1 上3 上1 下2 下1 上4 上1 上2 下1 下3 上1 下2 下1 下5 上1 上2 下1 上3 上1 下2 下1 下4 上1 上2 下1 下3 上1 下2 下1 下8 上1 上2 下1 上3 上1 下2 下1 上4 上1 上2 下1 下3 上1 下2 下1 上5 上1 上2 下1 上3 上1 下2 下1 下4 上1 上2 下1 下3 上1 下2 下1 上6 上1 上2 下1 上3 上1 下2 下1 上4 上1 上2 下1 下3 上1 下2 下1 下5 上1 上2 下1 上3 上1 下2 下1 下4 上1 上2 下1 下3 上1 下2 下1 下7 上1 上2 下1 上3 上1 下2 下1 上4 上1 上2 下1 下3 上1 下2 下1 上5 上1 上2 下1 上3 上1 下2 下1 下4 上1 上2 下1 下3 上1 下2 下1 下6 上1 上2 下1 上3 上1 下2 下1 上4 上1 上2 下1 下3 上1 下2 下1 下5 上1 上2 下1 上3 上1 下2 下1 下4 上1 上2 下1 下3 上1 下2 下1
一共341步
但是,玩过九连环的朋友都知道,从状态*11变成*00,或者从状态*00变成*11,只需要1步
也就是说,最右边的2个环可以一起装卸
这样,我们就需要调整输出了。
仔细说来,我们的目标是把所有的“下2 下1”换成“下21”,把所有的“上1 上2”换成“上12”
可以通过修改递归终止条件来实现,也可以把输出的结果入栈(或队列),在出栈(或队列)的时候进行合并,还可以直接把输出结果当成一个字符串,然后查找关键词替换。
查找关键词替换可以通过编程实现,也可以直接用word
总之结果如下:
下1 下3 上1 下21 下5 上12 下1 上3 上1 下21 下4 上12 下1 下3 上1 下21 下7 上12 下1 上3 上1 下21 上4 上12 下1 下3 上1 下21 上5 上12 下1 上3 上1 下21 下4 上12 下1 下3 上1 下21 下6 上12 下1 上3 上1 下21 上4 上12 下1 下3 上1 下21 下5 上12 下1 上3 上1 下21 下4 上12 下1 下3 上1 下21 下9 上12 下1 上3 上1 下21 上4 上12 下1 下3 上1 下21 上5 上12 下1 上3 上1 下21 下4 上12 下1 下3 上1 下21 上6 上12 下1 上3 上1 下21 上4 上12 下1 下3 上1 下21 下5 上12 下1 上3 上1 下21 下4 上12 下1 下3 上1 下21 上7 上12 下1 上3 上1 下21 上4 上12 下1 下3 上1 下21 上5 上12 下1 上3 上1 下21 下4 上12 下1 下3 上1 下21 下6 上12 下1 上3 上1 下21 上4 上12 下1 下3 上1 下21 下5 上12 下1 上3 上1 下21 下4 上12 下1 下3 上1 下21 下8 上12 下1 上3 上1 下21 上4 上12 下1 下3 上1 下21 上5 上12 下1 上3 上1 下21 下4 上12 下1 下3 上1 下21 上6 上12 下1 上3 上1 下21 上4 上12 下1 下3 上1 下21 下5 上12 下1 上3 上1 下21 下4 上12 下1 下3 上1 下21 下7 上12 下1 上3 上1 下21 上4 上12 下1 下3 上1 下21 上5 上12 下1 上3 上1 下21 下4 上12 下1 下3 上1 下21 下6 上12 下1 上3 上1 下21 上4 上12 下1 下3 上1 下21 下5 上12 下1 上3 上1 下21 下4 上12 下1 下3 上1 下21
刚好256步,256的2的8次方
实际上,无论是简化之前的341步,还是简化之后的256步,都满足f(n)=f(n-1)+2f(n-2)+1(这个递推式是比较显然的)
只不过简化之前的初始值是f(2)=2,f(3)=5,而简化之后的初始值是f(2)=1,f(3)=4
上面的2阶常系数齐次线性递推式还可以转化成1阶常系数齐次线性递推式。
对于状态000000000,要变成111111111,需要在main函数里面调用9次up函数
只需要修改main函数即可
代码:
int main()
{
for (int i = 0; i < 10; i++)l[i] =(i==0);
for (int i = 9; i>0; i--)up(i);
return 0;
}
结果:
上1 上2 下1 上3 上1 下2 下1 上4 上1 上2 下1 下3 上1 下2 下1 上5 上1 上2 下1 上3 上1 下2 下1 下4 上1 上2 下1 下3 上1 下2 下1 上6 上1 上2 下1 上3 上1 下2 下1 上4 上1 上2 下1 下3 上1 下2 下1 下5 上1 上2 下1 上3 上1 下2 下1 下4 上1 上2 下1 下3 上1 下2 下1 上7 上1 上2 下1 上3 上1 下2 下1 上4 上1 上2 下1 下3 上1 下2 下1 上5 上1 上2 下1 上3 上1 下2 下1 下4 上1 上2 下1 下3 上1 下2 下1 下6 上1 上2 下1 上3 上1 下2 下1 上4 上1 上2 下1 下3 上1 下2 下1 下5 上1 上2 下1 上3 上1 下2 下1 下4 上1 上2 下1 下3 上1 下2 下1 上8 上1 上2 下1 上3 上1 下2 下1 上4 上1 上2 下1 下3 上1 下2 下1 上5 上1 上2 下1 上3 上1 下2 下1 下4 上1 上2 下1 下3 上1 下2 下1 上6 上1 上2 下1 上3 上1 下2 下1 上4 上1 上2 下1 下3 上1 下2 下1 下5 上1 上2 下1 上3 上1 下2 下1 下4 上1 上2 下1 下3 上1 下2 下1 下7 上1 上2 下1 上3 上1 下2 下1 上4 上1 上2 下1 下3 上1 下2 下1 上5 上1 上2 下1 上3 上1 下2 下1 下4 上1 上2 下1 下3 上1 下2 下1 下6 上1 上2 下1 上3 上1 下2 下1 上4 上1 上2 下1 下3 上1 下2 下1 下5 上1 上2 下1 上3 上1 下2 下1 下4 上1 上2 下1 下3 上1 下2 下1 上9 上1 上2 下1 上3 上1 下2 下1 上4 上1 上2 下1 下3 上1 下2 下1 上5 上1 上2 下1 上3 上1 下2 下1 下4 上1 上2 下1 下3 上1 下2 下1 上6 上1 上2 下1 上3 上1 下2 下1 上4 上1 上2 下1 下3 上1 下2 下1 下5 上1 上2 下1 上3 上1 下2 下1 下4 上1 上2 下1 下3 上1 下2 下1 上7 上1 上2 下1 上3 上1 下2 下1 上4 上1 上2 下1 下3 上1 下2 下1 上5 上1 上2 下1 上3 上1
三,OJ实战
力扣 1611. 使整数变为 0 的最少操作次数
给你一个整数 n
,你需要重复执行多次下述操作将其转换为 0
:
- 翻转
n
的二进制表示中最右侧位(第0
位)。 - 如果第
(i-1)
位为1
且从第(i-2)
位到第0
位都为0
,则翻转n
的二进制表示中的第i
位。
返回将 n
转换为 0
的最小操作次数。
示例 1:
输入:n = 3 输出:2 解释:3 的二进制表示为 "11" "11" -> "01" ,执行的是第 2 种操作,因为第 0 位为 1 。 "01" -> "00" ,执行的是第 1 种操作。
示例 2:
输入:n = 6 输出:4 解释:6 的二进制表示为 "110". "110" -> "010" ,执行的是第 2 种操作,因为第 1 位为 1 ,第 0 到 0 位为 0 。 "010" -> "011" ,执行的是第 1 种操作。 "011" -> "001" ,执行的是第 2 种操作,因为第 0 位为 1 。 "001" -> "000" ,执行的是第 1 种操作。
提示:
0 <= n <= 109
思路:
就是九连环。递推式:
f(n*2)=f(n)*2+f(n)%2,f(n*2+1)=f(n)*2+1-f(n)%2
class Solution {
public:
int minimumOneBitOperations(int n) {
int x=n?minimumOneBitOperations(n/2):0;
return x*2 + (n%2 ? 1-x%2 : x%2);
}
};
POJ 1832 连环锁(九连环的推广)
题目:
许多人一定很熟悉九连环(如下图),九个环被串在一起,操作规则如下:第一个(右边)环可以任意装卸,如果第k个环没有被卸掉,而第k个环前边(右边)的所有环都被卸掉,则第k+1个环(第k个环左边的环)可以任意装卸(如果存在的话)。
用0表示此换被卸掉,1表示此环没有被卸掉,则九连环的每个状态可以用一个长度为9的二进制串来表示,如:111111001经过一次操作可以变成111111000,也可以变成111111011,111111111经过一次操作可以变成111111110,也可以变成111111101。
任务描述:
你现在要操作的是一个n连环,n为正整数,给出n连环的两种状态,计算出从第一种状态变换到第二种状态所需要的最少步数。
Input
第一行是一个正整数m,表示有m组测试数据。
每组测试数据一共3行,第一行是一个正整数n (0 < n < 128),后两行每一行描述一种状态,n个数(0或1),用空格隔开。
Output
对于每一组测试数据输出一行,一个非负整数,表示从第一种状态变换到第二种状态所需要的最少步数。
Sample Input
2
3
0 0 0
1 0 0
4
1 0 0 0
0 1 1 0
Sample Output
7
11
递推式并不难找,如果把状态看成1个二进制数,比如1000就是8,0110就是6,记为状态8、状态6
假设从状态n转移到转态0需要的最少步数为f(n),那么f就是上一题力扣1611的函数。
这样,只需要从左往右扫描就可以得出该状态的f()值,把2个状态的f()值相减即为2个转态之间转移的最少步数,这一点,可以从f()的定义(最小性)推导出来
代码:
import java.util.*;
import java.math.BigInteger;
public class Main
{
public static void main(String[] args)
{
Scanner cin = new Scanner(System.in);
int m=Integer.parseInt(cin.next());
while (m-->0)
{
int n=Integer.parseInt(cin.next());
BigInteger a=new BigInteger("0"),b=new BigInteger("0");
for(int i=0;i<n;i++)
if(Integer.parseInt(cin.next())==0)a=a.multiply(BigInteger.valueOf(2)).add(a.mod(BigInteger.valueOf(2)));
else a=a.multiply(BigInteger.valueOf(2)).add(BigInteger.valueOf(1)).subtract(a.mod(BigInteger.valueOf(2)));
for(int i=0;i<n;i++)
if(Integer.parseInt(cin.next())==0)b=b.multiply(BigInteger.valueOf(2)).add(b.mod(BigInteger.valueOf(2)));
else b=b.multiply(BigInteger.valueOf(2)).add(BigInteger.valueOf(1)).subtract(b.mod(BigInteger.valueOf(2)));
System.out.println(a.subtract(b).abs());
}
}
}