14天冲刺蓝桥杯省一day5

搜索剪枝
剪枝就好比走迷宫,提前预判哪些路是走不通的或者没必要走的直接砍掉。
剪枝的三种方式:1可行性剪枝:对于那些违反规则和超出范围的情况(即不可行),直接砍掉。就好比导航显示前面是一个死胡同,那根本就没有开进去的必要。 2重复性剪枝(不走回头路),核心逻辑是对于计算过的地方再次路过这个地方就不再算第二遍了。大白话说就是这条路我刚刚已经走过了,而且我拿一个小本本(记忆数组)把这个路是不可行给记录了下来,下次就不走这条路了。 3最优性剪枝,简而言之,如果我已经发现有一条路径去学校要花五分钟,那么在找一条新路的时候,如果这条新路走一半就已经花了5分钟了,那自然就没有往下走的必要了。 总结:1关注能不能走 2关注有没有算过 3关注划不划算。
例题:求解走完所有岛屿,到达终点所需要的最少时间。
解题思路:1到2岛需要10。2到1需要5.1到3需要20,3到1需要99.可以得知,可知去的时间和回来的时间都有可能其一是短的。那么现在有思路了,就是在这个岛屿的时候。如果直接走到下一个岛屿的时间很高,试一下往回走然后在这个岛屿直接往后走两步a,看看这样走的时间总和会不会比直接往后走少。
代码实现:核心三组变量,一是地图a[][],二是实时状态step(我在哪)记录这条路线要走多少步数,然后和minstep比较,book【】标记是否走过,po(记录我去过哪)他把走过的路压缩成二进制数,然后传给npostion数组。
三是外挂辅助,minstep,npostion(记忆化数组),position[20](位运算工具)。
step1使用dfs尝试所有路线。四步:第一步 尝试走,i=1先检查if(!book[i])说明这个点没走过,然后防止走太远if(step>=minstep),最后开始走book[i]=1;step+=距离;dfs(cur+1,i)递归调用。第二步 深入到底,在经过点1的时候会遇到一个for循环,然后查看下一个点,i=2发现book[2]=0,标记book[2]=1,再次调用dfs。
第三步,撞墙回头,if(cur+2==n)if (cur + 2 == n) {
// 计算最后一步到终点
step += a[qc][n - 1];//往后走一步,,走到终点。
if (step < minstep) minstep = step; // 更行最优路线长度。
step -= a[qc][n - 1]; // 回溯步数,往回走,然后剪掉这一部分的路程
return; // 函数结束,返回上一层。
}
dfs(cur + 1, i); // 刚才去探险了

// --- 下面是第四步:探险回来了 ---
po -= postion[i - 1]; // 1. 把钥匙还回去(状态回滚)
book[i] = 0; // 2. 把脚印擦掉(标记回滚)
step -= a[qc][i]; // 3. 把路费退回来(距离回滚)

step2重复性剪枝。 step3最优性剪枝
记忆化搜索
将函数计算结果缓存起来,以便在遇到相同输入的时候不会重复计算。还有就是如果已知中间点到终点的距离,那下次走到c点的时候,就可以不用在c点继续往下走了。直接把前面的点加上这个最短距离就行了。
如何实现记忆化搜索?
记忆化搜素=dfs的走法+dp的脑子。dfs实现简单,dp计算高效。
二分查找
题目描述
给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。
对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 0 开始计数)。
如果数组中不存在该元素,则返回 -1 -1。

输入格式
第一行包含整数 n 和 q,表示数组长度和询问个数。
第二行包含 n 个整数(均在 1 ~ 10000 范围内),表示完整数组。
接下来 q 行,每行包含一个整数 k,表示一个询问元素。

输出格式
共 q 行,每行包含两个整数,表示所求元素的起始位置和终止位置。
如果数组中不存在该元素,则返回 -1 -1。
这道题一个特殊的地方是如果一个元素出现多次,要返回他的起始位置和终止位置。所以这道题目需要查找第一个等于这个数字的数,还要查找最后一个等于这个数字的数。
寻找最大最小值模板(最好理解)。现在寻找最大值。那么我们规定,可行区在最大值的左边,这个可行区如果在左边,那么就移动左指针。然后l=0,r=n+1(数据范围是1到n)。规定l和r相邻的时候是最后一次循环,也即是l+1<r就结束循环。然后每次更新r和l都把mid给他们。最小值把可新区换一下,循环条件可以不用变。测试样例的第二个输入了4,但是可以看到4在数组里面是5,但是只有一个,所以应该输出5 5.但是你这个代码调试的时候输出了5 4。争对这个问题,该咋解决呢?题目的意思是左值要输出,右值也要输出。所以搞两个变量接住左值右值,然年分别输出不行了吗,但是还有一种情况,那就是没有找到,没有找到咋办呢?以及如何和找到的情况进行区分呢?使用if else肯定是没跑了,如果找到了就输出左值右值,如果没找到,就输出-1-1.那如何界定找没找到呢?找到就说明左值和右值等于目标值。不对啊,如果是这样,那不就忽略了只有单个值的情况了?因为单个值的时候,是被左/右?指向的。眉毛就是做指针指向着它。ok其实解决这个问题很简单,就是在写findmax和findmin函数的时候返回l和r时,多加一个条件就可以了。if(a[l]x) return l-1; else return -1; if(a[r]x) return r-1;else return -1;
二分答案
二分答案就是把二分查找的循环内的判断写成了一个check函数,if(a[mid]<x) l=mid;check函数如果是最大化查找那么return y<=c(即左值右移),y是a[mid],c就是target,x是mid。使用二分答案的前提不就是不确定a这个数组,然后就用二分答案呗。然后用x对应关系求出y。我们要不断迭代x的值,也就是mid然后修改l和r。那么如何找到y和x的关系呢?通过已知变量a[i]即每个树木的长度来求,

例题:题目描述
小华被大林叫去砍树,他需要砍倒 m 米长的木材。现在,小华弄到了一个奇怪的伐木机。伐木机工作过程如下: 小华设置一个高度参数 h(米),伐木机升起一个巨大的锯片到高度 h, 并锯掉所有的树比 h 高的部分(当然,树木不高于 h 米的部分保持不变)。小华就得到树木被锯下的部分。例如,如果一行树的高度分别为 20、15、10 和 17 米,小华把锯片升到 15 米的高度,切割后树木剩下的高度将是 15、15、10 和 15 米,而小华将从第 1 棵树得到 5 米, 从第 4 棵树得到 2 米,共得到 7 米木材。小华非常关注生态保护,所以他不会砍掉过多的木材。这正是他为什么尽可能高地设定伐木机锯片的原因。现在请你帮助小华找到伐木机锯片的最大的整数高度 h,使得他能得到的木材至少为 m 米。换句话说,如果再升高 1 米,则他将得不到 m 米木材。

输入格式

第 1 行 2 个整数 n 和 m, n 表示树木的数量, m 表示需要的木材总长度。

第 2 行 n 个整数,表示每棵树的高度,值均不超过 10⁹。保证所有木材长度之和大于 m, 因此必然有解。

输出格式

一行一个整数,表示砍树的最高高度。

这是一个经典的二分查找算法问题。核心思路是:在可能的锯片高度范围内(从0到最高树的高度),通过二分查找来快速定位满足“获得木材量 ≥ m”这个条件的最大高度 h。
要求找到锯片的最大高度。假设锯片的可行高度都存在数组里面,那么这个锯片的高度就是最大值,所以现在枚举一下锯片的高度,
锯片高度越高,用到的材料就越少,那么我们现在怎么求出y和x的关系呢?依然是通过已知求未知,已知是所有树木的高度,好的,那么我们如何利用这个高度,与x进行一个运算从而求出y呢。显然树的高度减去x(砍多少)就是y(砍下来)的长度。但是与一个前提条件就是树的高度减去x要为正数。由于这是一个减函数,所以最后return的时候一定是y>=m.以后遇到这种找最大值,最小值就往二分想就对了。如果没有给出具体关系,就要自己通过已知求y和x的关系。

双指针(尺取法)
双指针分为两种:解决序列问题,1同向扫描(两个指针都从头出发,但是j比i快,同向指针也称作快慢指针,在移动的时候会产生滑动窗口有灵活的运用) 2 反向扫描(i从头出发,j从末尾出发)
例题:例1. Luogu P1147 连续自然数和

对一个给定的正整数 M,求出所有的连续的正整数段(每一段至少有两个数),这些连续的自然数段中的全部数之和为 M。

例子:1998 + 1999 + 2000 + 2001 + 2002 = 10000,所以从 1998 到 2002 的一个自然数段为 M = 10000 的一个解。

输入格式

包含一个整数的单独一行给出 M 的值(10 leq M leq 2,000,000)。

输出格式

每行两个正整数,给出一个满足条件的连续正整数段中的第一个数和最后一个数,两数之间用一个空格隔开,所有输出行的第一个按从小到大的升序排列,对于给定的输入数据,保证至少有一个解。

右侧示例数据:

10000
18 142
297 328
388 412
1998 2002 如果有一个连续的序列,且这种求一个序列的子数组的左右端点的时候就可以优先考虑使用双指针。
例题二(2023 b)题目描述

程序猿圈子里正在流行一种很新的简写方法:

对于一个字符串,只保留首尾字符,将首尾字符之间的所有字符用这部分的长度代替。

例如 internationalization 简写成 i18n,Kubernetes 简写成 K8s,Langqiao 简写成 L5o 等。

在本题中,我们规定长度大于等于 K 的字符串都可以采用这种简写方法(长度小于 K 的字符串不配使用这种简写)。

给定一个字符串 S 和两个字符 c_1 和 c_2,请你计算 S 有多少个以 c_1 开头 c_2 结尾的子串可以采用这种简写?

已经增加10组hack数据,望周知

输入格式

第一行包含一个整数 K。

第二行包含一个字符串 S 和两个字符 c_1 和 c_2。

输出格式

一个整数代表答案。 窗口大小变化 k-字符串长度n 。 分析已知:k(字符串长度大于这个值,就可以使用简写)。s(字符串)。c1,c2:c1是子串的开头,c2是子串的结尾。 未知:有多少个以c1开头,c2结尾的子串可以简写。 样例:就是说以a开头b结尾而且要a和b之间的差距要大于等于k。 我现在想到的第一个思路就是先确定a(i指向它),然后用j往后去找b,直接从i+k的位置开始找,然后j不断往后,直到走完整个字符串,然后(这就是暴力循环,会超时,不可行)。 我们(多组)的前目标点和后目标点都是同样的,那么如果我们的后目标点往后移动的时候,确定了新的前目标点(第二组),这里有个问题第二组的前点是a,后点是b,但是有一个问题,第二组的千点a和第一组的后点b不也构成了一个组合。所以不能单纯的只是count++(只记录窗口内部的对应的组合,而是应该把前面的组合也算上)。这种第二组的前点和第一组的后点也可以组成一组的就应该用一个sum+=count。

include <bits/stdc++.h>

using namespace std;

typedef long long ll;

int k, cnt;
string s;
char a, b;

int main() {
// 1. 输入数据
cin >> k >> s >> a >> b;
int n = s.size();

ll ans = 0;
cnt = 0; // 初始化计数器,代表“可用的 a 的数量”

// 2. 正序遍历
// j 是 b 的位置(接收者)。
// j 必须从 k-1 开始,因为如果 j < k-1,那么 j 左边最远的 0 号位距离它都不足 k。
for (int j = k - 1; j < n; j++) {

// 计算与 j 刚好距离为 k 的那个 a 的位置(发起者)
// 距离为 k 意味着下标差为 k-1
int i = j - (k - 1);

// 【步骤一:进货】
// 检查这个新进入射程范围的 i 是不是 a
// 如果是,它以后对所有 j 都是合法的,所以加入池子
if (s[i] == a) {
cnt++;
}

// 【步骤二:出货】
// 检查当前的 j 是不是 b
// 如果是,它能和池子里目前所有的 a 配对(因为池子里的 a 距离 j 肯定 >= k)
if (s[j] == b) {
ans += cnt;
}
}

cout << ans;
return 0;
} 注意这里的i是随着j的变化而变化的。i并不是每次都从0开始的。(滑动窗口)。双指针的核心就是i和j都不能每一次循环都从0开始,也就是说i和j都不能在循环内部定义为0.
前缀和
前缀和适用于求区间和问题。通过先计算前缀和o(n),然后再计算区间和,那么前缀和该怎么计算呢?s[n]=s[n-1]+a[n];由这个等式可知,我们需要知道每个a[n]的值,才能求出来结果。求完前缀和,就需要利用前缀和计算区间和了,区间和利用S(l,r)=Sr-S(l-1).

二位前缀和.由一维前缀和的求法可知,我们现在要关心的问题是怎么求一个矩阵的和,然后怎么利用矩阵的和把这个区间矩阵的和求出来,这里面有怎样的数量关系呢?
前缀和 = 上区 + 左区 - 左上区 + 当前格

数学公式:注意i,j管辖的这条线之前的那个格子。
S_{i,j} = S_{i-1,j} + S_{i,j-1} - S_{i-1,j-1} + a_{i,j}

该公式是二维前缀和的标准计算公式,用于快速计算矩阵中任意子矩阵的元素和。其中:

S_{i,j} 表示从矩阵左上角 (1,1) 到位置 (i,j) 的子矩阵所有元素之和。
a_{i,j} 表示原矩阵中位置 (i,j) 的元素值。
公式通过“上区”、“左区”、“左上区”和“当前格”的加减组合,避免重复计算,实现高效累加。 图片中的文字内容如下:

区间和 = 全区 - 上区 - 左区 + 左上区

Sab,ij = Si,j - Sa-1,j - Si,b-1 + Sa-1,b-1

例题:长度为n的数列,常数k。要使用区间和。未知:有多少个k倍数区间(区间和是k的倍数)。直接按照常规前缀和模板走,然后找到一个符合条件的count++不就行了。求前缀和不会有变数,但是求子区间的时候,由于不知道左右区间在哪,如果要找到符合题目要求的区间,最直截了当的方法就是使用双重暴力循环遍历整个前缀和数组。cnt[sum[i] % k]++;//同起点计数的经典代码。

差分
例题引入:已知 序列an,和m组lrd(表示a l到r,但后对整个区间的数加上d)。未知:求相加后的整个序列。
差分数组核心三个原理:1 d[i]=a[i]-a[i-1],a[1]=1.默认为d[2]是第一个差值,即a[2]-a[1] 2a[i]=d[i]+d[i-1]+....d[1] 3将序列l到r区间加上d,那么差分数组变为d[l]+=x,d[r+1]-=x;这道题直接套这三个定理。轻松解决。先用1把差分数组求出来,再用3把加了d后的差分数组求得。最后2通过差分数组转换成最终结果的序列。

posted @ 2026-04-05 10:31  花捂嘴  阅读(14)  评论(0)    收藏  举报