蓝桥杯2023年省A(一波三折的)【买瓜】折半搜索+剪枝+排序

2024-03-17 09:12

本文主要是介绍蓝桥杯2023年省A(一波三折的)【买瓜】折半搜索+剪枝+排序,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

题目:洛谷 P9234 [蓝桥杯 2023 省 A] 买瓜


折半搜索

一开始觉得像dp,试着写了,显然过不了,但我实在觉得搜索也过不了啊,去看题解,发现使用了折半搜索(每天都觉得啥都不会捏
折半搜索就是先搜一半,记录下答案,再搜一半,最后把答案整合在一起
比如这题,每个数有三种选法,复杂度3n,折半后就是2*3n/2,大大降低了
但是这个复杂度还是过不了,后面就是不停优化剪枝

剪枝
剪枝1

当前和已经超过m (sum > m)

剪枝2

当前劈瓜次数已经超过历史最优 (k >= ans)

剪枝3

达到同样的和,曾经有过劈瓜数更小的方案 (mp.count(sum) && mp[sum] < k)

折半+剪枝(76分)
用unordered_map更快

#include <vector>
#include <iostream>
#include <cstdio>
#include <map> 
#include <unordered_map>
#include <ctime> 
using namespace std;typedef long long ll;
const int N = 35;ll a[N];
int ans = N;
unordered_map<ll, int> mp;//unordered_map更快
ll n, m;void dfs1(int i, int k, ll sum)
{if (sum > m) return;//超了,剪if (k >= ans) return;if (sum == m)//到了,走了{ans = min(ans, k);return; }if (mp.count(sum) && mp[sum] < k) return;//有过更优情况,剪!if (i > n / 2)//到头了,把搜到的东西记一下{//用map记录达到sum的砍刀数最小值if (mp.count(sum)) mp[sum] = min(mp[sum], k);else mp[sum] = k;return;}//先搜贡献大的更容易找到答案(大概吧dfs1(i + 1, k, sum + a[i] + a[i]);//整个dfs1(i + 1, k + 1, sum + a[i]);//半个dfs1(i + 1, k, sum); //不要
}void dfs2(int i, int k, ll sum)
{if (sum > m) return;if (k >= ans) return;if (sum == m){ans = min(ans, k);return; }if (i > n){//如果另一半有匹配的就更新答案if (mp.count(m - sum)) ans = min(ans, k + mp[m - sum]);return;}dfs2(i + 1, k, sum + a[i] + a[i]);dfs2(i + 1, k + 1, sum + a[i]);dfs2(i + 1, k, sum); 
}
int main()
{ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);cin >> n >> m;m += m;//为了不出现小数,把a[i]当半个瓜用,那么m也要翻倍for (int i = 1; i <= n; i++){cin >> a[i];}dfs1(1, 0, 0);dfs2(n/2+1, 0, 0);if (ans == N) cout << -1;else cout << ans;return 0;
}

继续看题解,发现很重要的一点是要给数组排序
为什么呢,不知道,大家都不说
好像挺有道理的但是我说不出道理,先放着

加上sort之后A了


觉得自己无敌了吧?来看看这个
题目 3145: 蓝桥杯2023年第十四届省赛真题-买瓜
欸~一模一样的代码只有82分
最后是怎么过的呢,要把数组从大到小排序
为什么呢
我的理解是,在搜第二段的时候,前面已经出现很多凑到m的情况,使ans变小了,所以第二段会被剪得更狠;相比之下第一段则几乎都要搜到头,所以重点在于要把第一段剪了。那有没有办法让第一段多剪掉一点呢,就让第一段都是大数,sum容易超过m,就会多多被剪了

加上从大到小排序:

sort(a + 1, a + n + 1, greater<int>()); 

正当我欢天喜地地准备结束这题的时候,我手欠地把从大到小版交到了洛谷
嘿——您猜怎么着?TLE了!
好吧我前边都是一顿瞎说(但我真是觉得有道理啊)
那只能是数据问题了,但是数据问题要怎么A啊!

剪枝4

后面我又翻看题解,发现了一个新的剪枝:
预处理后缀和,当前sum加上后缀和也够不到m的话就直接剪
注意这个剪枝只能在第一段中使用,因为第二段本身就要匹配第一段的答案,没法判断会不会不够

void dfs1(int i, int k, ll sum)
{if (sum > m) return;if (k >= ans) return;if (sum + suf[i] < m) return;//注意第i个还没加过,所以是判断sum + suf[i]if (sum == m){ans = min(ans, k);return; }if (i > n / 2){if (mp.count(sum)) mp[sum] = min(mp[sum], k);else mp[sum] = k;return;}dfs1(i + 1, k, sum + a[i] + a[i]);dfs1(i + 1, k + 1, sum + a[i]);dfs1(i + 1, k, sum); 
}

还有个注意点,要sort之后再算后缀和(对就是我这么傻
以及因为我存的a[i]算半个瓜,算后缀和要算整个瓜,所以要加两次

sort(a + 1, a + n + 1, greater<int>()); suf[n + 1] = 0;for (int i = n; i >= 1; i--){suf[i] = suf[i + 1] + a[i] + a[i];}

贴一个两边都能A的代码

#include <vector>
#include <iostream>
#include <cstdio>
#include <map> 
#include <unordered_map>
#include <ctime> 
#include <algorithm>
using namespace std;typedef long long ll;
const int N = 35;ll a[N], suf[N];
int ans = N;
unordered_map<ll, int> mp;
ll n, m;void dfs1(int i, int k, ll sum)
{if (sum > m) return;//超了,剪if (k >= ans) return;if (sum + suf[i] < m) return;//注意第i个还没加过,所以是判断sum + suf[i]if (sum == m)//到了,走了{ans = min(ans, k);return; }if (mp.count(sum) && mp[sum] < k) return;//有过更优情况,剪!if (i > n / 2)//到头了,把搜到的东西记一下{//用map记录达到sum的劈瓜数最小值if (mp.count(sum)) mp[sum] = min(mp[sum], k);else mp[sum] = k;return;}//先搜贡献大的更容易找到答案(大概吧dfs1(i + 1, k, sum + a[i] + a[i]);//整个dfs1(i + 1, k + 1, sum + a[i]);//半个dfs1(i + 1, k, sum); //不要
}void dfs2(int i, int k, ll sum)
{if (sum > m) return;if (k >= ans) return;if (sum == m){ans = min(ans, k);return; }if (i > n){//如果另一半有匹配的就更新答案if (mp.count(m - sum)) ans = min(ans, k + mp[m - sum]);return;}dfs2(i + 1, k, sum + a[i] + a[i]);dfs2(i + 1, k + 1, sum + a[i]);dfs2(i + 1, k, sum); 
}
int main()
{ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);cin >> n >> m;m += m;//为了不出现小数,把a[i]当半个瓜用,那么m也要翻倍for (int i = 1; i <= n; i++){cin >> a[i];}//sort(a + 1, a + n + 1); sort(a + 1, a + n + 1, greater<int>()); suf[n + 1] = 0;for (int i = n; i >= 1; i--){suf[i] = suf[i + 1] + a[i] + a[i];}dfs1(1, 0, 0);dfs2(n/2+1, 0, 0);if (ans == N) cout << -1;else cout << ans;return 0;
}

结果就是加上这个剪枝从大到小排序洛谷能过了,但是从小到大c语言网不行,继续看!


其他优化

我就不写了感觉好麻烦要把之前写的推翻重来orz

二分

不直接搜i+1,而是根据还需要的斤数在剩下的瓜里二分(因为已经排序了嘛),大于还需要的斤数的瓜可以不用考虑
(这样二分甚至可以不用折半搜索
见 P9234 [蓝桥杯 2023 省 A] 买瓜 题解

手写哈希表

大概是因为unordered_map还是常数大了吧(
见 买瓜 题解


参考

P9234 [蓝桥杯 2023 省 A] 买瓜 题解
买瓜 题解
买瓜题解


菜死我算了,真在赛场上碰到这种题我就拿个30分吧(默哀

这篇关于蓝桥杯2023年省A(一波三折的)【买瓜】折半搜索+剪枝+排序的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/818506

相关文章

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

hdu1240、hdu1253(三维搜索题)

1、从后往前输入,(x,y,z); 2、从下往上输入,(y , z, x); 3、从左往右输入,(z,x,y); hdu1240代码如下: #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#inc

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

usaco 1.3 Prime Cryptarithm(简单哈希表暴搜剪枝)

思路: 1. 用一个 hash[ ] 数组存放输入的数字,令 hash[ tmp ]=1 。 2. 一个自定义函数 check( ) ,检查各位是否为输入的数字。 3. 暴搜。第一行数从 100到999,第二行数从 10到99。 4. 剪枝。 代码: /*ID: who jayLANG: C++TASK: crypt1*/#include<stdio.h>bool h

usaco 1.3 Mixing Milk (结构体排序 qsort) and hdu 2020(sort)

到了这题学会了结构体排序 于是回去修改了 1.2 milking cows 的算法~ 结构体排序核心: 1.结构体定义 struct Milk{int price;int milks;}milk[5000]; 2.自定义的比较函数,若返回值为正,qsort 函数判定a>b ;为负,a<b;为0,a==b; int milkcmp(const void *va,c

hdu 1285(拓扑排序)

题意: 给各个队间的胜负关系,让排名次,名词相同按从小到大排。 解析: 拓扑排序是应用于有向无回路图(Direct Acyclic Graph,简称DAG)上的一种排序方式,对一个有向无回路图进行拓扑排序后,所有的顶点形成一个序列,对所有边(u,v),满足u 在v 的前面。该序列说明了顶点表示的事件或状态发生的整体顺序。比较经典的是在工程活动上,某些工程完成后,另一些工程才能继续,此时

hdu 4517 floyd+记忆化搜索

题意: 有n(100)个景点,m(1000)条路,时间限制为t(300),起点s,终点e。 访问每个景点需要时间cost_i,每个景点的访问价值为value_i。 点与点之间行走需要花费的时间为g[ i ] [ j ] 。注意点间可能有多条边。 走到一个点时可以选择访问或者不访问,并且当前点的访问价值应该严格大于前一个访问的点。 现在求,从起点出发,到达终点,在时间限制内,能得到的最大

AI基础 L9 Local Search II 局部搜索

Local Beam search 对于当前的所有k个状态,生成它们的所有可能后继状态。 检查生成的后继状态中是否有任何状态是解决方案。 如果所有后继状态都不是解决方案,则从所有后继状态中选择k个最佳状态。 当达到预设的迭代次数或满足某个终止条件时,算法停止。 — Choose k successors randomly, biased towards good ones — Close

hdu4277搜索

给你n个有长度的线段,问如果用上所有的线段来拼1个三角形,最多能拼出多少种不同的? import java.io.BufferedInputStream;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;

《数据结构(C语言版)第二版》第八章-排序(8.3-交换排序、8.4-选择排序)

8.3 交换排序 8.3.1 冒泡排序 【算法特点】 (1) 稳定排序。 (2) 可用于链式存储结构。 (3) 移动记录次数较多,算法平均时间性能比直接插入排序差。当初始记录无序,n较大时, 此算法不宜采用。 #include <stdio.h>#include <stdlib.h>#define MAXSIZE 26typedef int KeyType;typedef char In