递归与递推

说白了就是搜索算法和简单动归

递归实现指数型枚举

递归实现指数型枚举

解题思路:

主要就是深搜dfs,对于一个数,我们要么选择他要么不选择他就两种情况,搜够n层,然后输出就行

用u表示现在搜第u层,status表示当前选中的点,看成二进制数,第i位为0表示不选择i,为1表示选择i。

AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <bits/stdc++.h>
using namespace std;
int n;
// 当前可选择的数为u,状态位status当作二进制看,0的位置位不选,1的位置为选
void dfs(int u,int status){
if(u==n){
for(int i=0;i<n;i++)
{
if(status>>i&1)
cout<<i+1<<" ";
}
cout<<endl;
return;
}
// 不选u这个数
dfs(u+1,status);
// 选u这个数
dfs(u+1,status|1<<u);
}
int main(){
cin>>n;
dfs(0,0);
return 0;
}

递归实现组合枚举问题

递归实现组合枚举问题

解题思路

其实这个就是要从n个中选m个嘛,和第一题其实差不多的,但是要考虑当前的数量是不是m个,所以多加一个参数就可以了啊。

AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <bits/stdc++.h>
using namespace std;
int n,m;
// 当前选择第u个数,现在选了k个数,这k个数分别是status,status看成二进制数
void dfs(int u,int k,int status){
// 选中了m个数字
if(k == m){
for(int i =0;i<n;i++)
if(status>>i&1)
cout<<i+1<<' ';
cout<<endl;
return;
}
// 剩下可选的数字不可能再拿出m个
if(k+n-u < m || u == n)return;
// 要求从小到大,就要先找选择的
dfs(u+1,k+1,status|1<<u);
// 找不选的
dfs(u+1,k,status);
}
int main(){
cin>>n>>m;
dfs(0,0,0);
return 0;
}

费解的开关

费解的开关

解题思路

就是点击一个地方,其本身位置,上下左右都与原来的位置相反,问能否在6次内将其全部变成1

其实我们可以看第一行,对于第一行为0的位置

如果想让他变成1,是不是就需要点击他下面的那个按钮才会让他变成0并且不会影响第一行其他位置呢?

是的没错,只要我们点击他下面的按钮,就能完成,同理可以推演到第2行,第3行,第4行。

第5行不用点击,因为如果前4行全亮就够了,第五行如果不能全亮,说明这种情况下无法实现,让开关全亮。

但是,有没有可能存在存在整好点击第一行的某个位置,就全亮了或者说点击某个位置就能减少后面的步骤。

显然这种可能是存在的,因此我们还要判断点击第一行的按钮方式,我们可以用二进制的方式进行枚举,对于状态数,对应的位置为1的话,我们就认为当前这个按钮需要点击。否则就不用点击。

有5个位置,每个位置有2个选择,复杂度应该为25=322^5 =32

遍历4行,每行5个对应20

检验是否能成功,5个

最大检查灯阵个数,500个

最坏计算情况为:32×20×5×500=1,600,00032\times20\times5\times500=1,600,000属于可以接收的复杂度

AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <bits/stdc++.h>
using namespace std;
const int N =5;
const int INF = 1e9;
char g[N][N];
int n;

void turn(int x,int y){
// 点击按钮的坐标
// 中左上右下
int dx[] = {0,-1,0,1,0};
int dy[] = {0,0,1,0,-1};
for(int i =0;i<5;i++){
int a = x+dx[i],b = y+dy[i];
if(a>=0&&a<5&&b>=0&&b<5)
// 0变1,1变0
g[a][b] ^= 1;
}
}

int work(){
// 记录最佳点击次数
int ans = INF;
for(int i =0;i<1<<5;i++){
// 先记录一下这个灯的情况,因为会有不同的情况进行遍历,先做个备份,方便后面保存
char save[N][N];
memcpy(save,g,sizeof g);
// 首先需要点击第一行,因为可能存在只点击第一行就全亮了,或者有比不点击第一行更优的情况
// 因为不确定点击哪个地方会出现这种情况,所以尝试所有可能点击的方式
// 在这种情况下需要点击的次数
int cnt = 0;
// i看作一个二进制数,第k个位置为1的话,表示在这个位置上点一下
for(int k=0;k<N;k++){
if(i>>k&1){
cnt++;
turn(0,k);
}
}
// 开始看前四行,因为如果第四行如果有0,只能点击他下方第五行的位置进行修改,不然直接点第四行,上面亮了的地方又会出问题
for(int j = 0;j<4;j++)
for(int k = 0;k<N;k++){
// 如果当前位置为0,就要点击他下面的位置使得当前位置变成0
if(g[j][k] == '0'){
cnt++;
turn(j+1,k);
}
}
// 看看第五行是不是全部变成了1,是就更新我们的ans
bool flag = true;
for(int i= 0;i<N;i++){
if(g[N-1][i] == '0')
{
flag = false;
break;
}
}
// 第五行全为1,选择最小的次数
if(flag)ans = min(ans,cnt);
// 还原原图
memcpy(g,save,sizeof g);
}
return ans>6?-1:ans;
}

int main(){
cin>>n;
while(n--){
for(int i =0;i<N;i++)cin>>g[i];
cout<<work()<<endl;
}
return 0;
}

奇怪的汉诺塔问题

奇怪的汉诺塔

解题思路

仔细思考一下,如果有四根杆子,我们先移动前j个盘子到第2根杆上,是不是第2根杆就不能用了,然后问题就变成了i-j个盘子移动到第4根杆子上,只能有1,3,4三根杆子,这就变成了正常的汉诺塔问题。

文字看不懂就直接看代码,代码直接

初始条件都是,如果只有一个,那就只需要一步

先看看三个杆子的怎么算

three[i]表示i个盘子利用3根杆子i移动到任意一根使用的移动次数,首先得先将i-1个杆子移动到第2根杆子次数应该为three[i-1],然后最后一个盘子移动到第三根杆子,再将i-1个盘子移动到最后一根杆子上就行。

好三根杆子的弄完了,我们看看四根杆子怎么用

假设要移动i个盘子

先将前j个盘子移动到第2个位置,当前可以使用4个位置,因此移动次数为four[j]

然后将i-j个盘子移动到第4个位置,当前可以使用3个位置,因为第2个位置的盘子不可能i放下其他位置的盘子,所以移动次数为three[i-j]

最后再将前j个盘子移动到第4个位置,当前可使用的位置有4个,因此移动次数为four[i]

AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <bits/stdc++.h>
using namespace std;
int three[15],four[15];
int main(){
// 用于存放n个盘子,在3根杆子和4根杆子移动到目标杆子的最小步数
//初始化一下四个杆子的状态
memset(four,0x3f,sizeof four);
// 只有一个盘子的时候直接移动就行
three[1] = 1;
four[1] = 1;
for(int i = 2;i<=12;i++){
// three[i-1]前i-1个盘子移动到中间的杆子+1(最后一个杆子移动到最右边的杆子)+three[i-1](i-1个盘子移动到最右边的杆子)
three[i] = three[i-1]*2+1;
}
// 开始计算四根杆子的情况
for(int i = 2;i<= 12;i++){
for(int j = 0;j<i;j++){
// f[j]先将j个盘子移动到B此时可以用4个杆子f[j]
// 再将i-j个盘子移动到D,此时B杆子不能用了,只能用3根,可以用d[i-j]个盘子
// 最后把前j个盘子移动到D,这个时候又能用4根杆子了,f[j];
four[i] = min(four[i],four[j]*2+three[i-j]);
}
}
for(int i =1;i<=12;i++)cout<<four[i]<<endl;
return 0;
}

约数之和

约数之和

解题思路

本质上可以利用质因素进行计算求和结果T,如下公式会比较清晰,a1,a2,...,ana_1,a_2,...,a_n表示不同的质因数

T=(a10+a12+...+a1k)×(a20+a22+...+a2k)×...×(an0+an2+...+ank)T = (a_1^0+a_1^2+...+a_1^k)\times(a_2^0+a_2^2+...+a_2^k)\times...\times(a_n^0+a_n^2+...+a_n^k)

因为单个数还有计算其次方的可能,那质因数的可选个数会增大到k的b倍即kb,显然如果要使用之前写过的数论实现方式不太合适

首先,i的最后一个为奇数的情况,我们拿一个最推理,sum函数表示aia_i为i底,指数到k的加和,先取出他的一半,对右边的括号取出因数ak2+1a^{\frac k2+1},

sum(a1,k)=a10+a12+...+a1k=(a10+a12+...+ak2)+(ak2+1+...+a1k)=(a10+a12+...+ak2)+(a10+a12+...+ak2)×ak2+1=(a10+a12+...+ak2)(1+a1k2+1)=(1+a1k2+1)sum(a1,k2)sum(a_1,k) = a_1^0+a_1^2+...+a_1^k \\ = (a_1^0+a_1^2+...+a^{\frac k2})+(a^{\frac k2+1}+...+a_1^k) \\ =(a_1^0+a_1^2+...+a^{\frac k2})+(a_1^0+a_1^2+...+a^{\frac k2})\times a^{\frac k2+1} \\ =(a_1^0+a_1^2+...+a^{\frac k2})*(1+a_1^{\frac k2+1}) \\ =(1+a_1^{\frac k2+1})*sum(a_1,\frac k2) \\

举个例子

sum(a,3)=a0+a1+a2+a3k2=32=1,k2+1=32+1=2sum(a,3)=(a0+a1)+(a2+a3)sum(a,3)=(a0+a1)+a2×(a0+a1)sum(a,3)=(a0+a1)×(1+a2)=sum(a,1)×(1+a2)其中子函数中的1k2=32=1加和项的次方的2k2+1=32+1=2sum(a,3) = a^0+a^1+a^2+a^3\\ \lfloor\frac k2\rfloor = \lfloor\frac 32\rfloor=1,\lfloor\frac k2\rfloor+1 = \lfloor\frac 32+1\rfloor=2\\ sum(a,3) = (a^0+a^1)+(a^2+a^3)\\ sum(a,3) = (a^0+a^1)+a^2\times(a^0+a^1)\\ sum(a,3) = (a^0+a^1)\times(1+a^2)=sum(a,1)\times(1+a^2)\\ 其中子函数中的1是\lfloor\frac k2\rfloor = \lfloor\frac 32\rfloor=1\\ 加和项的次方的2是\lfloor\frac k2\rfloor+1 = \lfloor\frac 32+1\rfloor=2

显然,这是一个递归的过程,我们都指导任意非0数的0次方应该为1,这就作为我们递归的出口

上面就是奇数的情况,那如果是偶数呢?举个例子

sum(a,4)=a0+a1+a2+a3+a4sum(a,4)=a0+a×(a0+a1+a2)=a0+a×sum(a,3)这不就转化成奇数的形式了么?推演到一般情况就是sum(a,k)=1+a×sum(a,k1)当然要直接推奇数的也不难,但是经过测试,效率好像没有转化成偶数的高,所以如果你感兴趣可以自己推sum(a,4) = a^0+a^1+a^2+a^3+a^4\\ sum(a,4) = a^0+a\times(a^0+a^1+a^2)=a^0+a\times sum(a,3)\\ 这不就转化成奇数的形式了么?推演到一般情况就是\\ sum(a,k) = 1+a\times sum(a,k-1)\\ 当然要直接推奇数的也不难,但是经过测试,效率好像没有转化成偶数的高,所以如果你感兴趣可以自己推

AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <bits/stdc++.h>
using namespace std;
const int mod = 9901;
// 快速幂模板
int qmi(int a,int k){
a%=mod;
int res = 1;
while(k){
if(k&1)res = res*a%mod;
a = a*a%mod;
k>>=1;
}
return res;
}

int sum(int p,int k){
// 0次方是1
if(k==0)return 1;
// 如果个数是偶数
if(k%2==0)return (p%mod*sum(p,k-1)+1)%mod;
// 如果是奇数
return (1+qmi(p,k/2+1))*sum(p,k/2)%mod;
}

int main(){
int A,B;
cin>>A>>B;
int res = 1;
// 寻找质因数和对应的指数
for(int i =2;i<=A;i++){
int s= 0;
while(A%i == 0){
s++;
A/=i;
}
// 计算每个括号中的值
if(s)res = res*sum(i,s*B)%mod;
}
// 可能存在A为0的情况,这个是真的坑
if (!A) res = 0;
cout<<res<<endl;
return 0;
}

分形之城

分形之城

解题思路

先定义座标轴,左上角为原点,向下为x轴,向右为y轴,与数组的结构一致

令len为N-1级别单行的长度(2n12^{n-1})换成代码为1<<(n-1)

cnt为N-1级别的总个数(2n1×2n12^{n-1}\times2^{n-1}))换成代码为1<<2*(n-1)

我们先看等级1和等级2,我们可以看出,等级2可以分成四块,每一块都是由等级1的城市进行变化产生的,比如

  1. 等级2的左上角块,其实就是由等级1经过顺时针旋转90度,然后做一个水平翻转形成的
  2. 等级2的右上角块,就直接在y轴上平移等级1的长度
  3. 等级2的右下角块,就直接在x轴y轴上平移平移等级1的长度
  4. 等级2的左下角块,就先逆时针旋转90度,再做一个水平翻转,此时的1的坐标为仍在原点,为了方便平移,先将x,y向上和左平移一个长度,即-1,再将结果向下平移2个等级1长度,向右平移一个等级1的长度。

假设我们额坐标为(x,y)

  1. 左上角:先旋转$\theta=$90度,根据旋转公式,得到坐标(-y,x),根据y轴进行翻转,得到(y,x)

    [x,y]×[cosθsinθsinθsinθ]=[x,y]×[0110]=[y,x]\left[ x,y\right]\times \left[ \begin{matrix} cos\theta&sin\theta\\ -sin\theta&sin\theta\\ \end{matrix} \right]=\left[ x,y\right]\times \left[ \begin{matrix} 0&1\\ -1&0\\ \end{matrix} \right]=\left[-y,x\right]

  2. 右上角:(x,y)->(x,y+len)

  3. 右下角:(x,y)->(x+len,y+len)

  4. 左下角:先旋转θ=\theta=-90度,根据旋转公式得到坐标,(y,-x),根据y轴进行翻转,得到(-y,-x),先向左向右平移1个单位得到(-y-1,-x-1),再将结果向下平移2个等级1长度,向右平移一个等级1的长度得到(2*len-y-1,len-x-1)

将左上角,右上角,右下角,左下角分别定义为0,1,2,3

坐标变换弄好了,看代码吧,难的就是坐标变换了

AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define PLL pair<ll,ll>
PLL calc(ll n,ll m){
// 级别为0
if(n==0)return {0,0};
// n-1级别的长度和个数,这里记得要将1转化成ll不然会溢出
ll len = 1LL<<(n-1),cnt = 1LL<<(2*n-2);
// 获取上一级中点的相对位置
PLL pos = calc(n-1,m%cnt);
ll x = pos.first,y = pos.second;
// 获取当前坐标对应的分区
ll z = m/cnt;
// 如果在左上角
if(z==0)return {y,x};
// 如果在右上角
if(z==1)return {x,y+len};
// 如果在右下角
if(z==2)return {x+len,y+len};
// 如果在左下角
return {2*len-y-1,len-x-1};
}

int main(){
int t;
cin>>t;
while(t--){
// 数据大,最好都用ll
ll N,A,B;
cin>>N>>A>>B;
// 获取A的坐标,参数是级别,序号
PLL ac = calc(N,A-1);
// 获取B的坐标
PLL bc = calc(N,B-1);
// 计算两点间x,y轴的距离
ll x = ac.first-bc.first,y = ac.second-bc.second;
// 两点间距离公式,任意两个之间的距离为10
double res = sqrt(x*x+y*y)*10;
// 四舍五入
printf("%.0lf\n",res);
}
return 0;
}