1.递归-汉诺塔
问题描述
??我们的目的是要将整个塔移动到另一根桩柱上,每次只能移动一个圆盘,且较大的圆盘在移动过程中不能放置在较小的圆盘上面.问你具体的移动方案.
初步分析
?? 用递归的思想分析我们知道,对于最下面的盘子,如果上面的
n
?
1
n-1
n?1个盘子能够全部移动到柱
C
C
C上,我们就只需要将第n个盘子放到
B
B
B柱上,之后对在
C
C
C柱子上的
n
?
1
n-1
n?1个盘子做相同的操作将其移动到
B
B
B盘上,整个过程就完成了。
代码
void Move (int n , char a , char b , char c){
if (n == 1) {
cout << "第" << n << "号盘:" << a << "->" << b << endl;
return ;
}
Move(n - 1 , a , c , b);
cout << "第" << n << "号盘:" << a << "->" << b << endl;
Move(n - 1 , c , b , a);
}
int main()
{
int n;
while (cin >> n){
Move(n , 'A' , 'B' , 'C');
}
return 0;
}
复杂度分析
1.直接看输出行数,不难得出规律:
T
(
n
)
=
2
n
?
1
T(n)=2^n-1
T(n)=2n?1,则复杂度为
O
(
2
n
)
O(2^n)
O(2n).结合归纳法可证明
3.令
T
n
T_n
Tn?为
n
n
n个盘子的移动次数,那么
T
n
=
2
?
T
n
?
1
+
1
T_n=2*T_{n-1}+1
Tn?=2?Tn?1?+1,边界:
T
1
=
1
T_1=1
T1?=1
令
U
n
=
T
n
+
1
U_n=T_n+1
Un?=Tn?+1
则
U
n
=
2
?
T
n
?
1
+
1
+
1
=
2
(
T
n
?
1
+
1
)
=
2
U
n
U_n=2*T_{n-1}+1+1=2(T_{n-1}+1)=2U_n
Un?=2?Tn?1?+1+1=2(Tn?1?+1)=2Un?,边界:
U
1
=
T
1
+
1
=
2
U_1=T_1+1=2
U1?=T1?+1=2
所以
U
n
=
2
n
U_n=2^n
Un?=2n,所以
T
n
=
U
n
?
1
=
2
n
?
1
T_n=U_n-1=2^n-1
Tn?=Un??1=2n?1
进阶技巧
1.
n
=
1000
n=1000
n=1000时的移动次数:取模
??由于该递推式增加的太快了,如果要求具体解很容易爆long long,所以一般题目会要求取模.所以这里顺便给大家展开讲一下取模的技巧
1.对加减乘除操作的取模
int sub (int a , int b , int mod){
return ((a - b) % mod + mod) % mod;
}
int mul (int a , int b , int mod){
return ((a * b) % mod + mod) % mod;
}
int div (int a , int b , int mod){
return ((a / b) % mod + mod) % mod;
}
2.大数取模
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 3e5 +5;
const int mod = 1e9 + 7;
int main()
{
string a;
cin >> a;
int b = 0;
for (auto x : a){
int dig = x - '0';
b = (b * 10 + dig) % mod;
}
cout << b << endl;
return 0;
}
3.阶乘取模
阶乘取模,且模数比较小
x
!
?
%
?
m
x! \ \% \ m
x!?%?m
x
∈
[
1
,
1
0
1000000
]
,
m
=
6
x \in[1,10^{1000000}],m=6
x∈[1,101000000],m=6
结论: 当
x
>
=
m
x >= m
x>=m时,全部等于0.
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 3e5 +5;
const int mod = 1e9 + 7;
int main()
{
string a;
cin >> a;
int m = 666;
if (a.size() > 3) {
cout << 0 << endl;
return 0;
}
stringstream b;
b << a;
int c;
b >> c;
if (c >= 666){
cout << 0 << endl;
return 0;
}
int ans = 1;
for (int i = 1 ; i <= c ; i++){
ans = (ans * i) % m;
}
cout << ans << endl;
return 0;
}
2.
n
=
1000000000000
n=1000000000000
n=1000000000000时的移动次数:快速幂
目标:求解
2
n
2^n
2n 思路: ??1.先考虑求解一个这样的序列:
2
1
,
2
2
,
2
4
,
.
.
.
2^1,2^2,2^4,...
21,22,24,...,即数列:
x
i
=
2
2
i
?
1
x_i=2^{2^{i-1}}
xi?=22i?1
?? 2.我们发现
x
i
=
2
2
?
2
i
?
2
=
2
2
i
?
2
?
2
2
i
?
2
=
x
i
?
1
?
x
i
?
1
x_i=2^{2*2^{i-2}}=2^{2^{i-2}}*2^{2^{i-2}}=x_{i-1}*x_{i-1}
xi?=22?2i?2=22i?2?22i?2=xi?1??xi?1?
?? 3.对于
n
n
n,将其二进制分解得到:
n
=
a
0
2
0
+
a
1
2
1
+
.
.
.
+
a
k
2
k
,
???
a
i
∈
[
0
,
1
]
n=a_02^{0}+a_12^{1}+...+a_k2^{k},\ \ \ a_i \in [0,1]
n=a0?20+a1?21+...+ak?2k,???ai?∈[0,1]
则有:
2
n
=
2
a
0
?
2
0
+
a
1
2
1
+
.
.
.
+
a
k
2
k
=
2
a
0
?
2
0
?
2
a
1
?
2
1
?
.
.
.
?
2
a
k
?
2
k
2^{n}=2^{a_0*2^{0}+a_12^{1}+...+a_k2^{k}}=2^{a_0*2^{0}}*2^{a_1*2^{1}}*...*2^{a_k*2^{k}}
2n=2a0??20+a1?21+...+ak?2k=2a0??20?2a1??21?...?2ak??2k
对于等式左边,直接求是
O
(
n
)
O(n)
O(n)的,但是对于等式右边,我们发现它只有
O
(
l
o
g
n
)
O(logn)
O(logn)项的,而且这些项可以用第二个步骤的
x
i
=
x
i
?
1
?
x
i
?
1
x_i=x_{i-1}*x_{i-1}
xi?=xi?1??xi?1?来进行转移。
所以我们维护两个东西:
A
n
s
=
1
Ans=1
Ans=1代表最终答案,
x
x
x代表第二步中的序列.从低位到高位遍历
n
n
n的二进制位,当
a
i
=
1
a_i =1
ai?=1,则
A
n
s
?
=
x
Ans *= x
Ans?=x.否则就不乘.然后每一步转移
x
=
x
?
x
x=x*x
x=x?x,这就是快速幂的过程,代码如下所示.
int ksm (int a , int b , int mod){
int ans = 1 , x = a;
while (b){
if (b & 1) ans = ans * x % mod;
b >>= 1;
x *= x;
}
}
3.问题变形
??把有 n 个圆盘的塔从左边的桩柱A移动到右边的桩柱B,不允许在A和B之间直接移动,求最短的移动序列.(每一次移动都必须是移动到中间的桩柱或者从中间的桩柱移出.像通常一样,较大的圆盘永远不能放在较小圆盘的上面.)
分析
T
n
=
T
n
?
1
+
1
+
T
n
?
1
+
1
+
T
n
?
1
=
3
T
n
?
1
+
2
T_n=T_{n-1}+1+T_{n-1}+1+T_{n-1}=3T_{n-1}+2
Tn?=Tn?1?+1+Tn?1?+1+Tn?1?=3Tn?1?+2 变形: 求解
T
n
=
3
T
n
?
1
+
1
T_n=3T_{n-1}+1
Tn?=3Tn?1?+1,
T
1
=
1
T_1=1
T1?=1 推广: 求解
T
n
=
a
T
n
?
1
+
b
T_n=aT_{n-1}+b
Tn?=aTn?1?+b
证明为何
a
k
?
1
a
?
1
\frac{a^k-1}{a-1}
a?1ak?1?为何总是整数?
2.STL
不定长数组vector
#include<bits/stdc++.h>
using namespace std;
int a[100];
vector<int> b;
int main()
{
cout << b.size() << endl;
b.push_back(100);
b.pop_back();
cout << b[0] << endl;
for (int i = 0 ; i < 10 ; i++){
b.push_back(i);
}
for (auto g : b){
cout << g << " ";
}
cout << endl;
return 0;
}
测试: 1.用vector重写约瑟夫环。
字符串
定义:每个元素为字符的数组.
#include<bits/stdc++.h>
using namespace std;
int main()
{
string a , b;
cin >> a >> b;
cout << a + b << endl;
sort(a.begin() , a.end());
reverse(a.begin() , a.end());
return 0;
}
测试: 回文串判断
队列
#include<bits/stdc++.h>
using namespace std;
queue<int> q;
int main()
{
cout << q.size() << endl;
q.push(10);
cout << q.front() << endl;
q.pop();
return 0;
}
栈
#include<bits/stdc++.h>
using namespace std;
stack<int> s;
int main()
{
cout << s.size() << endl;
if (s.size() != 0)
cout << s.top() << endl;
s.push(10);
s.pop();
return 0;
}
测试: 1.括号匹配 https://www.luogu.com.cn/problem/P1739 ,求和
非线性结构:红黑树(set,map)
主要考虑如何使用它
1.set
一个自带<去重机制>的有序集合.
#include<bits/stdc++.h>
using namespace std;
set<int> s;
int main()
{
s.insert(5);
cout << s.size() << endl;
s.erase(5);
cout << *s.begin() << endl;
cout << *s.rbegin() << endl;
if (s.count(5))
else
for (auto g : s){
}
return 0;
}
预习
multiset: ??一个允许重复值出现的有序集合.
map: ??它的本质就是个数组.只是下标不一定是整数. map应用:
??1.给你一个长度为n的正整数。然后
Q
Q
Q次询问,每次询问你整数
x
(
x
≤
1
e
9
)
x(x \leq 1e9)
x(x≤1e9)出现了多少次.
??2.给你n个字符串。然后
Q
Q
Q次询问,每次询问你字符串
s
s
s出现了多少次.
??3.给你一个
n
?
m
(
n
,
m
≤
1
e
5
)
n*m(n,m \leq 1e5)
n?m(n,m≤1e5)的矩阵。
Q
(
≤
1
e
5
)
Q(\leq 1e5)
Q(≤1e5)次操作.每次操作在位置
(
x
,
y
)
(x,y)
(x,y)放置一个苹果.最后问你多少个格子有格子.
|