目录
- 汤姆斯的天堂梦(C++,Dijkstra)
- 题目描述
- 输入格式
- 输出格式
- 样例 #1
- 样例输入 #1
- 样例输出 #1
- 提示
- 解题思路:
- [蓝桥杯 2022 国 A] 环境治理(C++,Floyd)
- 题目描述
- 输入格式
- 输出格式
- 样例 #1
- 样例输入 #1
- 样例输出 #1
- 提示
- 解题思路:
- 跑步(C++,01背包方案数,DP)
- 题目描述
- 输入格式
- 输出格式
- 样例 #1
- 样例输入 #1
- 样例输出 #1
- 提示
- 数据规模与约定
- 解题思路:
- 遗址(C++,搜索)
- 题目描述
- 输入格式
- 输出格式
- 样例 #1
- 样例输入 #1
- 样例输出 #1
- 提示
- 解题思路:
- [蓝桥杯 2021 省 AB] 砝码称重(C++,01背包可行性)
- 题目描述
- 输入格式
- 输出格式
- 样例 #1
- 样例输入 #1
- 样例输出 #1
- 提示
- 解题思路:
汤姆斯的天堂梦(C++,Dijkstra)
题目描述
汤姆斯生活在一个等级为 0 0 0 的星球上。那里的环境极其恶劣,每天 12 12 12 小时的工作和成堆的垃圾让人忍无可忍。他向往着等级为 N N N 的星球上天堂般的生活。
有一些航班将人从低等级的星球送上高一级的星球,有时需要向驾驶员支付一定金额的费用,有时却又可以得到一定的金钱。
汤姆斯预先知道了从 0 0 0 等级星球去 N N N 等级星球所有的航线和需要支付(或者可以得到)的金钱,他想寻找一条价格最低(甚至获得金钱最多)的航线。
输入格式
第一行一个正整数 N N N( N ≤ 100 N \le 100 N≤100),接下来的数据可分为 N N N 个段落,每段的第一行一个整数 K i K_i Ki( K i ≤ 100 K_i \le 100 Ki≤100),表示等级为 i i i 的星球有 K i K_i Ki 个。
接下来的 K i K_i Ki 行中第 j j j 行依次表示与等级为 i i i,编号为 j j j 的星球相连的等级为 i − 1 i - 1 i−1 的星球的编号和此航线需要的费用(正数表示支出,负数表示收益,费用的绝对值不超过 1000 1000 1000)。
每行以 0 0 0 结束,每行的航线数 ≤ 100 \le 100 ≤100。
输出格式
输出所需(或所得)费用。正数表示支出,负数表示收益。
样例 #1
样例输入 #1
3
2
1 15 0
1 5 0
3
1 -5 2 10 0
1 3 0
2 40 0
2
1 1 2 5 3 -5 0
2 -19 3 -20 0
样例输出 #1
-1
提示
对于 100 % 100 \% 100% 的数据, 1 ≤ N ≤ 100 1 \le N \le 100 1≤N≤100, 1 ≤ K i ≤ 100 1 \le K_i \le 100 1≤Ki≤100。
样例解释:
解题思路:
单源最短路径,可以想到使用Dijkstra,但鉴于输入数据的特殊性,还需要做一些处理
首先,注意到本题每个节点是由等级和编号唯一确定的
但是我们仍可将其抽象为唯一的编号,如样例中的点可以标号为 1 1 1~ 8 8 8
然后,注意到本题中的边权可能为负数,要知道Dijkstra中的边权不能为负
把所有边权整体加上一个偏移量即可
那么存图的问题解决了,接下来就是套用Dijkstra算法了
AC代码如下
#include <iostream>
#include <vector>
#include <queue>
#include <memory.h>
using namespace std;
const int max_n = 100;
const int max_k = 100;
const int max_m = 100;
const int NaN = 0x3F3F3F3F;
class greater_queue {
public:
bool operator()(pair<int, int>p_1, pair<int, int>p_2) {
return p_1.first > p_2.first;
}
};
priority_queue<pair<int, int>, vector<pair<int, int>>, greater_queue>p_q;
struct edge { int v, p, next; }edges[max_n * max_k * max_m];//链式前向星
int head[max_n * max_k + 2] = { -1 };
int tot = -1;
int min_dist[max_n * max_k + 2] = { NaN };
bool book[max_n * max_k + 2] = { false };//最短路径集
void add_edge(int u, int v, int p) {//存图
edges[++tot] = { v,p,head[u] }; head[u] = tot;
}
void dijkstra() {
min_dist[1] = 0;
p_q.push(pair<int, int>(0, 1));//初始化
while (!p_q.empty()) {
int node = p_q.top().second;
int dist = p_q.top().first;
p_q.pop();
if (book[node]) continue;//已加入最短路径集
book[node] = true;//未加入最短路径集
for (int i = head[node]; i != -1; i = edges[i].next) {//尝试更新最短路径
int v = edges[i].v;
if (min_dist[v] > edges[i].p + dist) {
min_dist[v] = edges[i].p + dist;
p_q.push(pair<int, int>(min_dist[v], v));
}
}
}
}
int main() {
memset(head + 1, -1, sizeof(int) * (max_n * max_k + 1));
memset(min_dist + 1, 0x3F, sizeof(int) * (max_n * max_k + 1));//初始化
int n, k, u, p, cur_tot = 1, temp_tot = 0;
cin >> n;
for (int i = 1; i <= n; i++) {//存图
cin >> k;
for (int j = 1; j <= k; j++) {
++cur_tot;//节点分配
while (true) {
cin >> u;
if (u == 0) break;//行尾
cin >> p;
add_edge(temp_tot + u, cur_tot, p + 1000);//边权偏移
}
}
temp_tot = cur_tot - k;//寻访i-1级星球
}
dijkstra();
int ans = NaN;
for (int i = temp_tot + 1; i <= cur_tot; i++) {//寻找最小值
ans = min(ans, min_dist[i]);
}
cout << ans - 1000 * n << endl;//平衡偏移、输出
return 0;
}
[蓝桥杯 2022 国 A] 环境治理(C++,Floyd)
题目描述
LQ 国拥有 n n n 个城市,从 0 0 0 到 n − 1 n - 1 n−1 编号,这 n n n 个城市两两之间都有且仅有一条双向道路连接,这意味着任意两个城市之间都是可达的。每条道路都有一个属性 D D D,表示这条道路的灰尘度。当从一个城市 A 前往另一个城市 B 时,可能存在多条路线,每条路线的灰尘度定义为这条路线所经过的所有道路的灰尘度之和,LQ 国的人都很讨厌灰尘,所以他们总会优先选择灰尘度最小的路线。
LQ 国很看重居民的出行环境,他们用一个指标 P P P 来衡量 LQ 国的出行环境, P P P 定义为:
P = ∑ i = 0 n − 1 ∑ j = 0 n − 1 d ( i , j ) P=\sum \limits_{i=0}^{n-1} \sum \limits_{j=0}^{n-1} d(i,j) P=i=0∑n−1j=0∑n−1d(i,j)
其中 d ( i , j ) d(i,j) d(i,j) 表示城市 i i i 到城市 j j j 之间灰尘度最小的路线对应的灰尘度的值。
为了改善出行环境,每个城市都要有所作为,当某个城市进行道路改善时,会将与这个城市直接相连的所有道路的灰尘度都减少 1 1 1,但每条道路都有一个灰尘度的下限值 L L L,当灰尘度达到道路的下限值时,无论再怎么改善,道路的灰尘度也不会再减小了。
具体的计划是这样的:
- 第 1 1 1 天, 0 0 0 号城市对与其直接相连的道路环境进行改善;
- 第 2 2 2 天, 1 1 1 号城市对与其直接相连的道路环境进行改善;
……
- 第 n n n 天, n − 1 n - 1 n−1 号城市对与其直接相连的道路环境进行改善;
- 第 n + 1 n + 1 n+1 天, 0 0 0 号城市对与其直接相连的道路环境进行改善;
- 第 n + 2 n + 2 n+2 天, 1 1 1 号城市对与其直接相连的道路环境进行改善;
……
LQ 国想要使得 P P P 指标满足 P ≤ Q P \leq Q P≤Q。请问最少要经过多少天之后, P P P 指标可以满足 P ≤ Q P \leq Q P≤Q。如果在初始时就已经满足条件,则输出 0 0 0;如果永远不可能满足,则输出 − 1 -1 −1。
输入格式
输入的第一行包含两个整数 n , Q n, Q n,Q,用一个空格分隔,分别表示城市个数和期望达到的 P P P 指标。
接下来 n n n 行,每行包含 n n n 个整数,相邻两个整数之间用一个空格分隔,其中第 i i i 行第 j j j 列的值 D i , j ( D i , j = D j , i , D i , i = 0 ) D_{i,j} (D_{i,j}=D_{j,i},D_{i,i} = 0) Di,j(Di,j=Dj,i,Di,i=0) 表示城市 i i i 与城市 j j j 之间直接相连的那条道路的灰尘度。
接下来 n n n 行,每行包含 n n n 个整数,相邻两个整数之间用一个空格分隔,其中第 i i i 行第 j j j 列的值 L i , j ( L i , j = L j , i , L i , i = 0 ) L_{i,j} (L_{i,j} = L_{j,i}, L_{i,i} = 0) Li,j(Li,j=Lj,i,Li,i=0) 表示城市 i i i 与城市 j j j 之间直接相连的那条道路的灰尘度的下限值。
输出格式
输出一行包含一个整数表示答案。
样例 #1
样例输入 #1
3 10
0 2 4
2 0 1
4 1 0
0 2 2
2 0 0
2 0 0
样例输出 #1
2
提示
【样例说明】
初始时的图如下所示,每条边上的数字表示这条道路的灰尘度:
此时每对顶点之间的灰尘度最小的路线对应的灰尘度为:
- d ( 0 , 0 ) = 0 , d ( 0 , 1 ) = 2 , d ( 0 , 2 ) = 3 d(0, 0) = 0, d(0, 1) = 2, d(0, 2) = 3 d(0,0)=0,d(0,1)=2,d(0,2)=3;
- d ( 1 , 0 ) = 2 , d ( 1 , 1 ) = 0 , d ( 1 , 2 ) = 1 d(1, 0) = 2, d(1, 1) = 0, d(1, 2) = 1 d(1,0)=2,d(1,1)=0,d(1,2)=1;
- d ( 2 , 0 ) = 3 , d ( 2 , 1 ) = 1 , d ( 2 , 2 ) = 0 d(2, 0) = 3, d(2, 1) = 1, d(2, 2) = 0 d(2,0)=3,d(2,1)=1,d(2,2)=0。
初始时的 P P P 指标为 ( 2 + 3 + 1 ) × 2 = 12 (2 + 3 + 1) \times 2 = 12 (2+3+1)×2=12,不满足 P ≤ Q = 10 P \leq Q = 10 P≤Q=10;
第一天, 0 0 0 号城市进行道路改善,改善后的图示如下:
注意到边 ( 0 , 2 ) (0, 2) (0,2) 的值减小了 1 1 1,但 ( 0 , 1 ) (0, 1) (0,1) 并没有减小,因为 L 0 , 1 = 2 L_{0,1} = 2 L0,1=2 ,所以 ( 0 , 1 ) (0, 1) (0,1) 的值不可以再减小了。此时每对顶点之间的灰尘度最小的路线对应的灰尘度为:
- d ( 0 , 0 ) = 0 , d ( 0 , 1 ) = 2 , d ( 0 , 2 ) = 3 d(0, 0) = 0, d(0, 1) = 2, d(0, 2) = 3 d(0,0)=0,d(0,1)=2,d(0,2)=3,
- d ( 1 , 0 ) = 2 , d ( 1 , 1 ) = 0 , d ( 1 , 2 ) = 1 d(1, 0) = 2, d(1, 1) = 0, d(1, 2) = 1 d(1,0)=2,d(1,1)=0,d(1,2)=1,
- d ( 2 , 0 ) = 3 , d ( 2 , 1 ) = 1 , d ( 2 , 2 ) = 0 d(2, 0) = 3, d(2, 1) = 1, d(2, 2) = 0 d(2,0)=3,d(2,1)=1,d(2,2)=0。
此时 P P P 仍为 12 12 12。
第二天,1 号城市进行道路改善,改善后的图示如下:
此时每对顶点之间的灰尘度最小的路线对应的灰尘度为:
- d ( 0 , 0 ) = 0 , d ( 0 , 1 ) = 2 , d ( 0 , 2 ) = 2 d(0, 0) = 0, d(0, 1) = 2, d(0, 2) = 2 d(0,0)=0,d(0,1)=2,d(0,2)=2,
- d ( 1 , 0 ) = 2 , d ( 1 , 1 ) = 0 , d ( 1 , 2 ) = 0 d(1, 0) = 2, d(1, 1) = 0, d(1, 2) = 0 d(1,0)=2,d(1,1)=0,d(1,2)=0,
- d ( 2 , 0 ) = 2 , d ( 2 , 1 ) = 0 , d ( 2 , 2 ) = 0 d(2, 0) = 2, d(2, 1) = 0, d(2, 2) = 0 d(2,0)=2,d(2,1)=0,d(2,2)=0。
此时的 P P P 指标为 ( 2 + 2 ) × 2 = 8 < Q (2 + 2) \times 2 = 8 < Q (2+2)×2=8<Q,此时已经满足条件。
所以答案是 2 2 2。
【评测用例规模与约定】
- 对于 30 % 30\% 30% 的评测用例, 1 ≤ n ≤ 10 1 \leq n \leq 10 1≤n≤10, 0 ≤ L i , j ≤ D i , j ≤ 10 0 \leq L_{i,j} \leq D_{i,j} \leq 10 0≤Li,j≤Di,j≤10;
- 对于 60 % 60\% 60% 的评测用例, 1 ≤ n ≤ 50 1 \leq n \leq 50 1≤n≤50, 0 ≤ L i , j ≤ D i , j ≤ 1 0 5 0 \leq L_{i,j} \leq D_{i,j} \leq 10^5 0≤Li,j≤Di,j≤105;
- 对于所有评测用例, 1 ≤ n ≤ 100 1 \leq n \leq 100 1≤n≤100, 0 ≤ L i , j ≤ D i , j ≤ 1 0 5 0 \leq L_{i,j} \leq D_{i,j} \leq 10^5 0≤Li,j≤Di,j≤105, 0 ≤ Q ≤ 2 31 − 1 0 \leq Q \leq 2^{31} - 1 0≤Q≤231−1。
蓝桥杯 2022 国赛 A 组 F 题。
解题思路:
emm这题虽然描述有点长,但实际思路并不难想
因为对于 P P P的定义已经给出很明显的提示了——多源最短路径,用Floyd
然后就是本题的图的特殊之处:灰尘度的变化
这个特殊之处直接导致了想一次Floyd直接解决问题是不可能的
因为Floyd得到的最短路径抽象去了路径上的点,那样就不知道哪条最短路径会缩短了
所以要想其他办法
去思考本题的答案会发现:
1)天数越多,就越可能达标
2)本题要求的是最少需要多少天
这不就是二分法嘛
然后就明白了大概的解题思路:二分搜索天数,用Floyd判断这天的灰尘度是否达标
接下来就是一些细节的问题了,例如
对于完全图用二维数组存图、每轮搜索之前都需要根据天数初始化图的边权
计算得到天数的范围是 0 0 0~ 1 0 7 10^7 107
以及最重要的,关于数据范围的问题
q
q
q的最大值已经超出了int
所能达到的精度,应该用long long
存储
即使是long long
,累加结束之后也可能溢出(
边权和
m
a
x
=
1
5
3
边权和max = 1^{5^3}
边权和max=153),故需要加上正数判断
说了这么多,最后,AC代码如下
//Floyd
#include <iostream>
using namespace std;
const int max_n = 100;
const int max_l = 1e5;
const int max_d = 1e5;
const long long max_q = 0xFFFFFFFF - 1;
const int NaN = 0x3F3F3F3F;
int map[max_n][max_n] = { 0 };//存图
int limit[max_n][max_n] = { 0 };//最小灰尘度
int ans = -1;
int n;
long long q;
int dist[max_n][max_n] = { 0 };//临时图
bool floyd(int day) {
for (int i = 0; i < n; i++) {//初始化
for (int j = 0; j < n; j++) {
dist[i][j] = max(map[i][j] - day / n - (day % n >= i + 1 ? 1 : 0), limit[i][j]);
}
}
for (int i = 0; i < n; i++) {//Floyd
for (int j = 0; j < n; j++) {
for (int z = 0; z < n; z++) {
dist[j][z] = min(dist[j][z], dist[j][i] + dist[i][z]);
}
}
}
long long sum = 0;
for (int i = 0; i < n; i++) {//累加
for (int j = 0; j < n; j++) {
sum += dist[i][j];
}
}
if (sum >= 0 && sum <= q) {//溢出判断 && 达标
ans = day;
return true;
}
else return false;
}
void bin_search() {//二分
int left = -1, right = max_n * max_l + 1;
while (left + 1 != right) {
int middle = (left + right) / 2;
if (floyd(middle)) {
right = middle;
}
else {
left = middle;
}
}
}
int main() {
cin >> n >> q;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
cin >> map[i][j];
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
cin >> limit[i][j];
bin_search();
cout << ans;
return 0;
}
跑步(C++,01背包方案数,DP)
题目描述
路人甲准备跑 n n n 圈来锻炼自己的身体,他准备分多次( > 1 \gt1 >1)跑完,每次都跑正整数圈,然后休息下再继续跑。
为了有效地提高自己的体能,他决定每次跑的圈数都必须比上次跑的多。
可以假设他刚开始跑了 0 0 0 圈,那么请问他可以有多少种跑完这 n n n 圈的方案?
输入格式
一行一个整数,代表 n n n。
输出格式
一个整数表示跑完这 n n n 圈的方案数。
样例 #1
样例输入 #1
212
样例输出 #1
995645335
提示
数据规模与约定
对于 100 % 100\% 100% 的数据,保证 5 ≤ n ≤ 500 5\le n\le 500 5≤n≤500。
解题思路:
首先,这是一道01背包求方案数问题,接下来说明为什么
1)可以把要跑的圈数 n n n看作背包容量
2)可以把 1 1 1~ n n n圈看作 n n n个不同体积的物品
3)由于要跑的圈数必须一次多于一次,所以对于每个物品,只有选和不选两种情况
那么如何解决01背包求方案数问题呢?
我们先从**最简单的dfs
**开始
int dfs(int w, int n) {//w为当前物品,n为背包容量
if (n == 0) return 1;//找到方案
if (w == 0) return 0;//未找到
if (n >= w) return dfs(w - 1, n - w) + dfs(w - 1, n);//选 + 不选
else return dfs(w - 1, n);//不选
}
思路是不是很简单?但是效率也很感人,所以我们需要进行优化
最容易想到的就是记忆了:w
和n
一定,dfs(w, n)
一定
int mem[max_n + 1][max_n + 1] = {0};//记忆
int dfs(int w, int n) {//w为当前物品,n为背包容量
if (n == 0) return 1;//找到方案
if (w == 0) return 0;//未找到
if (mem[w][n]) return mem[w][n];//记忆
if (n >= w) return mem[w][n] = (dfs(w - 1, n - w) + dfs(w - 1, n));//选 + 不选
else return mem[w][n] = dfs(w - 1, n);//不选
}
当然,优化力度还是不够
注意到这个问题存在最优子结构,所以我们可以动态规划
for (int i = 0; i <= n; i++) mem[i][0] = 1;//初始化
for (int i = 1; i <= n; i++) {//i号物品
for (int j = 1; j <= n; j++) {//j背包容量
if (j >= i) mem[i][j] = mem[i - 1][j] + mem[i - 1][j - i];
else mem[i][j] = mem[i - 1][j];
}
}
现在时间上已经可以通过了,接下里进行空间优化
背包问题的空间优化自然少不了滚动数组
注意到j < i
时,有mem[i][j] = mem[i - 1][j]
,所以可以不修改直接合并为一行
注意到j >= i
时,有mem[i][j] = mem[i - 1][j] + mem[i - 1][j - i]
,即同列的上一行元素+同行左侧元素
故循环式可以修改为
for (int i = 1; i <= n; i++)
for (int j = n; j >= i j--)
mem[j] += mem[j - i];
至此,01背包求方案数问题解决了,AC代码如下
还有一件事,十年OI一场空,不开long long见祖宗,记得要防止溢出qwq
#include <iostream>
using namespace std;
const int max_n = 500;
long long mem[max_n + 1] = { 0 };//记忆
int main() {
mem[0] = 1;//初始化
int n;
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = n; j >= i; j--)
mem[j] += mem[j - i];
cout << mem[n] - 1;
return 0;
}
(为什么要cout << mem[n] - 1;
?记得回去看一眼题目哦)
遗址(C++,搜索)
题目描述
很久很久以前有一座寺庙,从上往下看寺庙的形状正好是一个正方形,由 4 4 4 个角上竖立的圆柱搭建而成。现在圆柱都倒塌了,只在地上留下圆形的痕迹,可是现在地上有很多这样的痕迹,专家说一定是最大的那个。
写一个程序,给出圆柱的坐标,找出由 4 4 4 个圆柱构成的最大的正方形,因为这就是寺庙的位置,要求计算出最大的面积。注意正方形的边不一定平行于坐标轴。
例如图有 10 10 10 根柱子,其中 ( 4 , 2 ) , ( 5 , 2 ) , ( 5 , 3 ) , ( 4 , 3 ) (4,2),(5,2),(5,3),(4,3) (4,2),(5,2),(5,3),(4,3) 可以形成一个正方形, ( 1 , 1 ) , ( 4 , 0 ) , ( 5 , 3 ) , ( 2 , 4 ) (1,1),(4,0),(5,3),(2,4) (1,1),(4,0),(5,3),(2,4) 也可以,后者是其中最大的,面积为 10 10 10。
输入格式
第一行包含一个 N ( 1 ≤ N ≤ 3000 ) N(1\leq N\leq 3000) N(1≤N≤3000),表示柱子的数量。
接下来 N N N 行,每行有两个空格隔开的整数表示柱子的坐标(坐标值在 0 0 0 到 5000 5000 5000 之间),柱子的位置互不相同。
输出格式
如果存在正方形,输出最大的面积,否则输出 0 0 0。
样例 #1
样例输入 #1
10
9 4
4 3
1 1
4 2
2 4
5 8
4 0
5 3
0 5
5 2
样例输出 #1
10
提示
【数据范围】
30 % 30\% 30% 满足: 1 ≤ N ≤ 100 1\leq N \leq100 1≤N≤100。
60 % 60\% 60% 满足: 1 ≤ N ≤ 500 1\leq N \leq500 1≤N≤500。
100 % 100\% 100% 满足: 1 ≤ N ≤ 3000 1\leq N \leq3000 1≤N≤3000。
解题思路:
思路很简单:枚举每一个正方形,找出其中 S S S最大的
问题在于如何枚举每一个正方形
我们可以通过两个点来确认一个正方形
原理:正方形任意一条边的两个端点的横坐标之差、纵坐标之差均相等
AC代码如下
#include <iostream>
using namespace std;
const int max_x = 5000;
const int max_y = 5000;
const int max_n = 3000;
bool map[max_x * 2 + 1][max_y * 2 + 1] = { false };//存图
struct node { int x, y; }nodes[max_n + 1];//枚举节点
long long ans = 0;
long long calc_S(node n_1, node n_2) {//计算面积
return (long long)((n_1.x - n_2.x) * (n_1.x - n_2.x)) +
(long long)((n_1.y - n_2.y) * (n_1.y - n_2.y));
}
void search_max(const int n) {
for (int i = 1; i <= n; i++) {//枚举正方形
for (int j = 1; j <= n; j++) {
int x_1 = nodes[i].x, y_1 = nodes[i].y;
int x_2 = nodes[j].x, y_2 = nodes[j].y;
int t_1 = x_1 - x_2, t_2 = y_1 - y_2;
if (x_1 + t_2 < 0 || y_1 - t_1 < 0 || x_2 + t_2 < 0 || y_2 - t_1 < 0) continue;//越界判断
if (map[x_1][y_1] && map[x_2][y_2] && map[x_1 + t_2][y_1 - t_1] && map[x_2 + t_2][y_2 - t_1])
ans = max(ans, calc_S(nodes[i], nodes[j]));
}
}
}
int main() {
int n, x, y;
cin >> n;
for (int i = 1; i <= n; i++) {//存图
cin >> x >> y;
nodes[i].x = x; nodes[i].y = y;
map[x][y] = true;
}
search_max(n);
cout << ans;
return 0;
}
注:1)图需要开大一些,防止正越界
2)枚举正方形时需要加上判断,防止负越界
3)计算面积无需开根号,勾股定理结果为边长的平方(即为 S S S)
4)计算结果需要是long long
数据类型
[蓝桥杯 2021 省 AB] 砝码称重(C++,01背包可行性)
题目描述
你有一架天平和 N N N 个砝码, 这 N N N 个砝码重量依次是 W 1 , W 2 , ⋯ , W N W_{1}, W_{2}, \cdots, W_{N} W1,W2,⋯,WN 。 请你计算一共可以称出多少种不同的重量?
注意砝码可以放在天平两边。
输入格式
输入的第一行包含一个整数 N N N 。
第二行包含 N N N 个整数: W 1 , W 2 , W 3 , ⋯ , W N W_{1}, W_{2}, W_{3}, \cdots, W_{N} W1,W2,W3,⋯,WN 。
输出格式
输出一个整数代表答案。
样例 #1
样例输入 #1
3
1 4 6
样例输出 #1
10
提示
【样例说明】
能称出的 10 种重量是: 1 、 2 、 3 、 4 、 5 、 6 、 7 、 9 、 10 、 11 1 、 2 、 3 、 4 、 5 、 6 、 7 、 9 、 10 、 11 1、2、3、4、5、6、7、9、10、11 。
1 = 1 2 = 6 − 4 ( 天平一边放 6 , 另一边放 4) 3 = 4 − 1 4 = 4 5 = 6 − 1 6 = 6 7 = 1 + 6 9 = 4 + 6 − 1 10 = 4 + 6 11 = 1 + 4 + 6 \begin{aligned} &1=1 \\ &2=6-4(\text { 天平一边放 } 6, \text { 另一边放 4) } \\ &3=4-1 \\ &4=4 \\ &5=6-1 \\ &6=6 \\ &7=1+6 \\ &9=4+6-1 \\ &10=4+6 \\ &11=1+4+6 \end{aligned} 1=12=6−4( 天平一边放 6, 另一边放 4) 3=4−14=45=6−16=67=1+69=4+6−110=4+611=1+4+6
【评测用例规模与约定】
对于 50 % 50 \% 50% 的评测用例, 1 ≤ N ≤ 15 1 \leq N \leq 15 1≤N≤15 。
对于所有评测用例, 1 ≤ N ≤ 100 , N 1 \leq N \leq 100, N 1≤N≤100,N 个砝码总重不超过 1 0 5 10^5 105。
蓝桥杯 2021 第一轮省赛 A 组 F 题(B 组 G 题)。
解题思路:
对于任意一个砝码,有三种情况:
1)不放
2)放左侧(与物品同侧)
3)放右侧(与物品异侧)
我们从n
号砝码开始,遍历每一种情况,也就是暴力搜索DFS
void dfs(int i, int sum) {//前i号物品
if (i == 0) {
if (sum > 0) {
if (!book[sum]) {
ans++;
book[sum] = true;
}
}
return;
}
dfs(i - 1, sum);//不选
dfs(i - 1, sum - w[i]);//选,左侧
dfs(i - 1, sum + w[i]);//选,右侧
}
接下来进行时间优化,最容易想到的是记忆
记忆策略:i
和sum
相同,停止搜索直接return
(剪枝)
因为sum < 0
会发生数组越界,所以初始调用dfs(n, max_w)
,加上一个偏移量即可
void dfs(int i, int sum) {//前i号物品
if (book[i][sum]) return;
book[i][sum] = true;
if (i == 0) {
if (sum > max_w) ans++;
return;
}
dfs(i - 1, sum);//不选
dfs(i - 1, sum - w[i]);//选,左侧
dfs(i - 1, sum + w[i]);//选,右侧
}
对于本题的数据,加上记忆之后的代码已经够用了,但是我们仍可以进一步优化
方法仍然是背包问题中常见的动态规划
但从当前写出来的DFS是看不出如何动态规划的,所以我们修改一下,尝试理解下面的代码
void dfs(int i, int sum) {//前i号物品
book[sum] = true;
if (i == 0) return;
dfs(i - 1, sum);//不选
dfs(i - 1, sum - w[i]);//选,左侧
dfs(i - 1, sum + w[i]);//选,右侧
}
最后输出答案的步骤如下
dfs(n, sum);
for (int i = sum + 1; i <= 2 * sum; i++) ans += book[i];
cout << ans;
并不难懂吧qwq
那么接下来所谓的动态规划,其实就是把没有返回后操作的递归,变成循环而已
/* 大多数没有返回后操作的递归,都可以转化为循环的形式 */
book[0][sum] = true;//初始化
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= 2 * sum; j++) {
if (book[i - 1][j]) book[i][j] = true;//不选
else if (j - w[i] >= 0 && book[i - 1][j - w[i]]) book[i][j] = true;//选,右侧
else if (j + w[i] <= 2 * sum && book[i - 1][j + w[i]]) book[i][j] = true;//选,左侧
}
}
要注意到动态规划的状态转移方向与递归是相反的
然后还可以把循环主体的部分的分支结构改为顺序结构,进一步提升效率
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= 2 * sum; j++) {
book[i % 2][j] = (book[(i - 1) % 2][j]) || //不选
(j - w[i] >= 0 && book[(i - 1) % 2][j - w[i]]) || //选,左侧
(j + w[i] <= 2 * sum && book[(i - 1) % 2][j + w[i]]);//选,右侧
}
}
接下来,就是背包问题中很常见的滚动数组了,利用i % 2
的性质把数组压缩到二维
AC代码如下
//01背包可行性
#include <iostream>
using namespace std;
const int max_n = 100;
const int max_w = 1e5;
int w[max_n + 1] = { 0 };//砝码
bool book[2][2 * max_w + 1] = { false };//滚动数组
int ans = 0;
int main() {
int n, sum = 0;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> w[i];
sum += w[i];
}
book[0][sum] = true;//初始化
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= 2 * sum; j++) {
book[i % 2][j] = (book[(i - 1) % 2][j]) || //不选
(j - w[i] >= 0 && book[(i - 1) % 2][j - w[i]]) || //选,左侧
(j + w[i] <= 2 * sum && book[(i - 1) % 2][j + w[i]]);//选,右侧
}
}
for (int i = sum + 1; i <= 2 * sum; i++) ans += book[n % 2][i];
cout << ans;
return 0;
}
这样就从递归时的 132 132 132ms -> 动态规划时的 88 88 88ms