本周编程学习笔记:
周末玩嗨了,忘记发了,私密马赛
1.广搜BFS-走迷宫(最短路径)
其实就是洪水填充,下面是相关代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<bits/stdc++.h>
using namespace std;
//BFS 走迷宫
#define int long long
#define double long double
const int maxn = 1e6 + 100;
const double eps = 1e-5;
//上下左右
int dx[] = { 0,0,-1,1 };
int dy[] = { -1,1,0,0 };
int mapp[100][100], vis[100][100];
int ok = 0;
struct point {
int x, y;
int step;
};
queue<point> s;
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
int n, m; cin >> n >> m;
//输入地图
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> mapp[i][j];
}
}
int startx, starty, endx, endy; cin >> startx >> starty >> endx >> endy;
//BFS
//初始化起点并入队
point start; start.x = startx, start.y = starty, start.step = 1;
s.push(start); vis[startx][starty] = 1;//vis 打标记,记录已经走过的点
//go
while (!s.empty()) {
int x = s.front().x, y = s.front().y;
if (x == endx && y == endy) {
ok = 1;
cout << s.front().step << endl;
break;
}
for (int choose = 0; choose <= 3; choose++) {
int tx = x + dx[choose], ty = y + dy[choose];
if (mapp[tx][ty] == 0 && vis[tx][ty] == 0) {
point tmp; tmp.x = tx, tmp.y = ty, tmp.step = s.front().step + 1;
s.push(tmp); vis[tx][ty] = 1;
}
}
s.pop();
}
if (ok == 0) {
cout << "no ans" << endl;
}
return 0;
}
/*[输入样例]
3 3
0 0 0
1 0 0
0 0 1
1 1 3 1
*/
/*[输出]
5
*/
二维数组map 用来存储迷宫的地形,二维数组vis 用来记忆已经走过的路线(在之后进行洪水填充的时候会用到)。
一维数组 dx 和 dy 用来控制人在迷宫之中运动的方向(上、下、左、右)。
变量ok 用来判断有无成功走到出口(该迷宫是否为死迷宫)。
进行BFS的时候我们用到了 STL 自带的 队列queue ,里面放着一个个 point 结构体,内部存储了 该点的横纵坐标与已经走过的步数(在到达终点后进行输出)。需要注意的就是起点需要自己组装一个结构体变量丢进去,之后就交给洪水填充就行了(详见代码)
2.动态规划——完全背包问题
【洛谷 B3635】硬币问题
题目描述
今有面值为 1、5、11 元的硬币各无限枚。
想要凑出 n_n_ 元,问需要的最少硬币数量。
输入格式
仅一行,一个正整数 n_n_。
输出格式
仅一行,一个正整数,表示需要的硬币个数。
输入输出样例
输入 #1复制15
输出 #1复制3
输入 #2复制12
输出 #2复制2
说明/提示
样例解释
对于样例数据 1,最佳方案是 15=5+5+515=5+5+5,使用到 3 枚硬币。
对于样例数据 2,最佳方案是 12=11+112=11+1,使用到 2 枚硬币。
数据规模与约定
对于 100%100% 的数据,保证 n≤106_n_≤106。
【使用 dp 解决】
#define _CRT_SECURE_NO_WARNINGS 1
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define double long double
const int maxn = 1e6 + 100;
const double eps = 1e-5;
//B3635
signed main() {
int a[3] = { 1,5,11 };
int n; cin >> n;
vector<int>dp(n + 1, 0x3f3f3f3f);//0x3f3f3f3f 是一个很大的数 默认需要无穷的金币
dp[0] = 0;//需要的金币的数量
for (int i = 0; i < 3; i++) {//遍历每一种金币
for (int j = a[i]; j <= n; j++) {// j 是 钱的sum
dp[j] = min(dp[j - a[i]] + 1, dp[j]);//类似记忆
}
}
cout << dp[n] << endl;//输出组成金额n 所需要的最少金币的数量
return 0;
}
//也可以使用 BFS
可能有人刚看到这道题目的时候会想使用贪心算法(先取值面最大的硬币,之后再取次之的,以此类推),但是众所周知,贪心算法仅能解决特定的问题,比如说我们想要凑齐 8块钱 ,我们有不限量的 1元 4元 5元 ,如果我们用贪心的话 就会是 5+1+1+1 = 8 ,总共用了 4枚硬币,但是其实只需要 4+4 = 8 ,2枚硬币即可。所以这里用贪心不太恰当,而是要用动态规划来解决。
动态规划有点类似于之前的周记里面提到过的记忆法,先解决小问题,然后在小问题的基础上进步解决大问题,有效地避免了相同问题地重复运行,大大地压缩了时间
完全背包问题的特征就是我们每一个元素都可以重复使用多次,我们在进行dp时,二重循环就需要来顺序遍历,以便我们每一个元素都可以进行重复使用,与 01背包问题不同的是,01背包问题的特征就是其的每一个元素都只能使用有限次,就必须在二重循环时进行逆序遍历以防止同一元素被重复运用多次(本题中因为明确要求了每一种类型的金币可以无限使用,所以是完全背包问题)
总结:1.完全背包顺序 2.01背包逆序
3.动态规划——01背包问题
【洛谷 P1048】采药
题目描述
辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
如果你是辰辰,你能完成这个任务吗?
输入格式
第一行有 22 个整数 T_T_(1≤T≤10001≤_T_≤1000)和 M_M_(1≤M≤1001≤_M_≤100),用一个空格隔开,T_T_ 代表总共能够用来采药的时间,M_M_ 代表山洞里的草药的数目。
接下来的 M_M_ 行每行包括两个在 11 到 100100 之间(包括 11 和 100100)的整数,分别表示采摘某株草药的时间和这株草药的价值。
输出格式
输出在规定的时间内可以采到的草药的最大总价值。
输入输出样例
输入 #1复制
70 3
71 100
69 1
1 2
输出 #1复制3
说明/提示
【数据范围】
- 对于 30%30% 的数据,M≤10_M_≤10;
- 对于全部的数据,M≤100_M_≤100。
【题目来源】
NOIP 2005 普及组第三题
#define _CRT_SECURE_NO_WARNINGS 1
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define double long double
const int maxn = 1e6 + 100;
const double eps = 1e-5;
//P1048
signed main() {
int T, M; cin >> T >> M;
vector<int>dp(T + 10, 0);
for (int i = 0; i < M; i++) {
int w, c; cin >> w >> c;
for (int j = T; j >= 0; j--) {//为什么要倒序而不是像之前金币那样顺序,是为了不重复放入同样的药材!
if (j < w) break; //在 5_4_0 那道放金币的题目里,每一种类的金币都有无数枚,与这道题目的不同。
dp[j] = max(dp[j - w] + c, dp[j]);
}
}
cout << dp[T] << endl;
return 0;
}
在完全背包那边已经说的蛮详细了,这里就不再多加赘述了。不理解的话可以用纸笔来手动“运行一下”,或者打个断点调试一下,理解了的话就通畅了。
4.01背包问题变型
【洛谷 P1802】5倍经验日
题目背景
现在乐斗有活动了!每打一个人可以获得 5 倍经验!absi2011 却无奈的看着那一些比他等级高的好友,想着能否把他们干掉。干掉能拿不少经验的。
题目描述
现在 absi2011 拿出了 x_x_ 个迷你装药物(嗑药打人可耻…),准备开始与那些人打了。
由于迷你装药物每个只能用一次,所以 absi2011 要谨慎的使用这些药。悲剧的是,用药量没达到最少打败该人所需的属性药药量,则打这个人必输。例如他用 22 个药去打别人,别人却表明 33 个药才能打过,那么相当于你输了并且这两个属性药浪费了。
现在有 n_n_ 个好友,给定失败时可获得的经验、胜利时可获得的经验,打败他至少需要的药量。
要求求出最大经验 s_s_,输出 5s5_s_。
输入格式
第一行两个数,n_n_ 和 x_x_。
后面 n_n_ 行每行三个数,分别表示失败时获得的经验 losei_losei_,胜利时获得的经验 wini_wini_ 和打过要至少使用的药数量 usei_usei_。
输出格式
一个整数,最多获得的经验的五倍。
输入输出样例
输入 #1复制
6 8
21 52 1
21 70 5
21 48 2
14 38 3
14 36 1
14 36 2
输出 #1复制1060
说明/提示
【Hint】
五倍经验活动的时候,absi2011 总是吃体力药水而不是这种属性药。
【数据范围】
- 对于 10%10% 的数据,保证 x=0_x_=0。
- 对于 30%30% 的数据,保证 0≤n≤100≤_n_≤10,0≤x≤200≤_x_≤20。
- 对于 60%60% 的数据,保证 0≤n,x≤1000≤_n_,_x_≤100, 10<losei,wini≤10010<losei,_wini_≤100,0≤usei≤50≤_usei_≤5。
- 对于 100%100% 的数据,保证 0≤n,x≤1030≤_n_,_x_≤103,0<losei≤wini≤1060<_losei_≤_wini_≤106,0≤usei≤1030≤_usei_≤103。
【题目来源】
fight.pet.qq.com
absi2011 授权题目
#define _CRT_SECURE_NO_WARNINGS 1
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define double long double
const int maxn = 1e6 + 100;
const double eps = 1e-5;
//P1802
signed main() {
int T, M; cin >> M >> T;
vector<int>dp(T + 10, 0);
for (int i = 0; i < M; i++) {
int w, a, b; cin >> b >> a >> w;
for (int j = T; j >= 0; j--) {
/*dp[j] = dp[j] + b;
if (j < w) break;
dp[j] = max(dp[j - w] + a, dp[j] + b);*/
if (j >= w) {
dp[j] = max(dp[j - w] + a, dp[j] + b);
}
else {
dp[j] = dp[j] + b;
}
}
}
cout << dp[T] * 5 << endl;
return 0;
}
此处的 01背包问题 与上面的 01背包问题不同之处就是就算其不会因为 胜利而加分,其即使失败了也会进行加分,所以需要考虑本次胜利加分对整体合算还是失败 加分合算。(详见代码)