集训周记 第五周

本周编程学习笔记:

周末玩嗨了,忘记发了,私密马赛

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背包问题不同之处就是就算其不会因为 胜利而加分,其即使失败了也会进行加分,所以需要考虑本次胜利加分对整体合算还是失败 加分合算。(详见代码)

  • 21
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值