2021-03-13

寒假回顾

递推和递归
1.递推:已知到未知(初始化前项)
2.递归:未知到已知
所有项皆可求
一 递推
铺砖问题
析:可以考虑最后一块砖怎么铺 与前面的关系
*******##
*********(’#'下的两个元素已确定)
①横铺a[i] = a[i - 2];
********#
********#
②竖铺a[i] = a[i - 1];
综上所述,a[i] = a[i - 1] + a[i - 2];(横竖两种情况)
把每一块砖作为最后一块砖
代码见下
#include
int a[100];
int main() {
int n;
scanf("%d", &n);
a[1] = 1;
a[2] = 2;
a[0] = 1;
for (int i = 2; i <= n; i++) {
a[i] = a[i - 1] + a[i - 2];
}
printf("%d", a[n]);
return 0;
}
递推的重点在递推式,一般由循环递推

二.递归
寻找此解有关的前解, 简化问题的过程,路线:递归简化找到初始已知解的问题, 一步步返回问题。
要点:①出口, ②简化过程
求n!的值
析:从递归的角度分析, n!=(n-1)!*n(寻找此项与前的关系)
出口:n == 1; reutrn 1;
代码如下
#include
long long int f(int n) {
if (n == 1)//出口
return 1;
return n * f(n - 1);//简化
}
int main() {
int a;
scanf("%d", &a);
printf("%d!=%lld", a, f(a));
return 0;
}

三.分治算法
思想:把一个问题分解成若干个子问题,减小问题规模,直至子问题规模较小易解决时求解
运用递归
粉刷栅栏

小明家有一道栅栏,由N块竖立的木板构成,每块的宽度均为1,高度为H_i,相邻木板之间没有缝隙,紧密地连在一起形成一道栅栏墙。

栅栏需要刷漆,小明的刷子的宽度为1。我们把落下刷子连续刷动(中间不能提起刷子)直到提起刷子称为"一笔"。这与写字时的笔画数概念相同。

小明可以竖着刷漆,一笔把一块木板从底到顶刚好刷完。他也可以横着刷漆,一笔把连续的若干块木板刷完一条宽度为1的范围,如果中间遇到高度上的缺口,则这一笔结

束。刷过漆的部分允许重复刷漆。

求:小明刷满整块栅栏墙最少需要多少笔。

输入格式
第1行:1个整数N,表示木板的数量
第2行:N个整数,分别表示每块木板的高度Hi

输出格式
第1行:1个整数,表示答案。
析:刷栅栏有两种方法:横刷,竖刷

    • *

**** #***

#***

如果横刷:笔数=横刷笔数+剩余部分最少笔数
反之:笔数=所有栅栏数
思路出来了
代码如下
#include
#include
using namespace std;
int h[500005], n;
int f(int l, int r) {//l, r 当前栅栏左右端点
if (l > r)//没有栅栏
return 0;
if (l == r) {//一个栅栏
if (h[l] != 0)
return 1;
else
return 0;
}
int min1 = 2147483647, t;
for (int i = l; i <= r; i++) {
if (h[i] < min1)
min1 = h[i], t = i;
}
for (int i = l; i <= r; i++) {//横刷
h[i] -= min1;
}
return min(r - l + 1, min1 + f(l, t - 1) + f(t + 1, r));
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &h[i]);
}
printf("%d", f(1, n));
return 0;
}
分治重点在分解问题, 递归出口:
if 达到最小子问题 (解决);
else 继续分解;

四.快速排序
思想:在序列列中找任意一个基准数,把比它大的放右边,比它小的放左边,使基准数归位,递归操作左右两个序列 直至序列长度只有一个数 结束
备注:以下为升序做法,降序类似
基准数归位的过程(双指针)
i从左端点开始,j从右端点开始
i找到第一个大于基准数的,j找到第一个小于基准数的
交换i,j;
直至i,j相遇或错过
while (i < j) {
while (j > i && a[j] >= x) j–;
while (i < j && a[i] <= x) i++;
swap(a[i], a[j]);//交换
}
swap(a[s], a[i]);//交换
代码如下
#include
#include
using namespace std;
int a[300005], n;
int f(int s, int t) {
int i = s;
int j = t;
int x = a[s];//x为基准数//以第一个数为基准数
while (i < j) {
while (j > i && a[j] >= x) j–;
while (i < j && a[i] <= x) i++;
swap(a[i], a[j]);//交换
}
swap(a[s], a[i]);//交换
return i;
}
void ff(int s, int t) {
if (s < t) {
int i = f(s, t);
ff(s, i);
ff(i + 1, t);
}
return;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
ff(1, n);
for (int i = 1; i <= n; i++) {
printf("%d ", a[i]);
}
return 0;
}

五.归并排序
思想:把序列递归地分解为若干个子序列,直至子序列自身有序(只有一个元素),再一一合并
此处示范为升序
分解方法(递归二分)(从中点)
合并方法
A: a b c (A,B为有序的)
i
B: a1 b1 c1
j
while (i <= mid && j <= r) {
if (a[i] <= a[j])
b[k] = a[i++];//b为合并后的数组(辅助数组)
else
b[k] = a[j++];
k++;
}
while (i <= mid) b[k++] = a[i++];//若剩余,直接接在后面
while (j <= r) b[k++] = a[j++];

代码如下
#include
#include
using namespace std;
int a[100005], b[100005], n, ans;
void ff(int l, int mid, int r) {//合并
int i = l, k = l;
int j = mid + 1;
while (i <= mid && j <= r) {
if (a[i] <= a[j])
b[k] = a[i++];//b为合并后的数组(辅助数组)
else
b[k] = a[j++];
k++;
}
while (i <= mid) b[k++] = a[i++];
while (j <= r) b[k++] = a[j++];
for (int i = l; i <= r; i++) a[i] = b[i];
}
void f(int l, int r) {
if (l == r)
return;
int mid = (l + r) / 2;
f(l, mid);
f(mid + 1, r);
ff(l, mid, r);
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
f(1, n);
for (int i = 1; i <= n; i++) {
printf("%d ", a[i]);
}
return 0;
}
插一道搜索
有n名同学要乘坐摆渡车从人大附中前往人民大学,第i位同学在第t[i]分钟去等车。只有一辆摆渡车在工作,但摆渡车容量可以视为无限大。摆渡车从人大附中出发、把车上的同学送到人民大学、再回到人大附中(去接其他同学),这样往返一趟总共花费m分钟(同学上下车时间忽略不计)。摆渡车要将所有同学都送到人民大学。

凯凯很好奇,如果他能任意安排摆渡车出发的时间,那么这些同学的等车时间之和最小为多少呢?

注意:摆渡车回到人大附中后可以即刻出发。
作为一道搜索的题目,首先构建搜索的框架
int dfs(int i, int T) //i为当前人的编号,T为发车时间
递归出口即是(i == n + 1)(遍历完所有的人)
接下来,考虑搜索过程,目前有两种情况
①发车时间在t[i]前 if (t[i] > T) T = t[i] (从当前人出发)
② 首先发车时间有可能是如图所示的
@@*****&
(t[i]) (t[i+1]) (发)
∴int j = i;
while (j <= n && t[j] <= T) sum += t[j];//(sum记录等待的时间)
int Min = T * (j - 1 - i + 1) - sum + dfs(j, T + m) //(如果从当前人断开*,等待的最小值)
相信接下来必定是从后面的人断开了
while (j <= n) {
Min = min(Min, ((j - i) * t[j] - sum + dfs(j + 1, t[j] + m)));
sum += t[j++];
}

动态规划
最长上升子序列
我们先定义dp数组
dp[i]为以 i 结尾的最长子序列的长度
那么,有两种情况
①接续(找到可以接续的中长度最长的子序列接续)
②若无可接续,长度为一
代码如下
#include
#include
#include
using namespace std;
int n, a[1005], dp[1005], m;
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
dp[1] = 1;
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= i - 1; j++) {
if (a[j] < a[i]) {
dp[i] = max(dp[i], dp[j]);
}
}
dp[i]++;
m = max(m, dp[i]);
}
printf("%d", m);
return 0;
}

但这种方法时间复杂度很高(花太多时间接续)
有一点贪心在里面,在选择时,我们要让子序列末尾尽量地小。
可以说定义dp[i]为长度为i的序列末尾的最小值
阶段为i
决策有两种①替换(让末尾最小值尽量地小)
②接续(末尾为当前数)
关于替换 我们要让末尾尽量小,可以替换从1~n第一个比它大的数
温馨提示:关于找第一个比它大的数时,用二分

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值