1 条题解
-
0
自动搬运
来自洛谷,原作者为

guoshengyu1231
**搬运于
2025-08-24 23:14:17,当前版本为作者最后更新于2025-04-24 19:19:29,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
题解:P12280 [蓝桥杯 2024 国 Python A] 特别的数组
方法一
思路
考虑到答案具备单调性,首先考虑二分答案。 那为什么这题能用二分答案呢? 首先我们要明白什么是二分答案?
其实通俗的讲,如果说暴力枚举是一个一个猜答案的话,那二分答案就是将答案用二分查找的方式找出来。 具体的,给定一个答案的范围,然后每次会算出这个范围的中间数 。接下来会有一个 函数来检测猜到的 是否满足题目要求,接着会根据实际情况适当修改值的范围。到最后只有一个数,就是最终的答案(其实就跟二分查找差不多,只不过查找的是答案)。
那这就不得不提到二分答案的使用条件了。 二分答案的使用需要同时满足以下几个核心条件:
-
解空间有序:问题的答案必须存在于一个有序的范围内(如数值区间),且存在明确的单调性特征。也就是:
- 当求解最大值时,若某个值 满足条件,则所有比 小的值也满足条件。
- 当求解最小值时,若某个值 满足条件,则所有比 大的值也满足条件。
例如,问题中出现“最大值最小化”或“最小值最大化”等双最值描述时,通常适用二分答案。
-
存在高效的判定函数:对于给定的候选答案 ,需要能在多项式时间(通常为 或 )内验证其是否满足题目要求。
-
判定逻辑独立于答案生成:判定函数的设计应仅依赖当前候选答案 ,无需预先知道其他可能的解。这种独立性使得每次二分迭代只需关注当前 的可行性。
其实说了一大堆,都只是前置知识。 那么我们现在回到原题,首先看了看题目,发现满足二分答案的使用条件,所以可以使用二分答案。既然是二分答案,那么我们还是先考虑影响答案的因素吧。其实这个并不难,题目要求删除一个区间,使最终的序列最长且里面的数互不相同。那影响删除的区间的因素有区间的左右边界和区间的长度。但是他们三个中只需要确定两个就可以确定区间了。那我们该怎么选择呢?那接下来我们就要考虑那个条件是与答案有直接关系的,也就是他的优劣能确定答案的优劣,那显然是区间的长度,所以我们不妨二分删除区间的长度,在 函数中枚举删除区间的左端点。
实现
代码的大致框架有了,接下来需要思考如何去实现这个算法。首先二分答案部分比较简单,我们可以先不管。重点是如何去实现 函数。首先我们统计原数组中每个数出现的次数,在统计有哪些数字出现了不止一次。然后枚举移动区间的左端点,并实时记录那些数字被删除了,那些数字又恢复了。总之就是枚举一个滑动的窗口,如果有一次所有的数字都最多只出现了一次,那就说明这个答案是可行的,具体详见代码。
代码
#include<bits/stdc++.h> using namespace std; const int maxn=1e5+5; int n,a[maxn]; bool check(int x) { int cnt[maxn],s=0; memset(cnt,0,sizeof cnt); for(int i=x+1;i<=n;i++)//覆盖了第一个区间,故从x+1开始计数 { cnt[a[i]]++; if(cnt[a[i]]==2) s++; } if(s==0) return true; for(int i=1;i+x<=n;i++) { cnt[a[i+x]]--; if(cnt[a[i+x]]==1) s--; cnt[a[i]]++; if(cnt[a[i]]==2) s++; //滑动窗口 if(s==0) return true; } return false; } int main() { ios::sync_with_stdio(0); cin.tie(0); cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; int l=0,r=n; while(l<r) { int mid=l+r>>1; if(check(mid)) r=mid; else l=mid+1; } cout<<n-l; return 0; }方法二
其实还有更简单且更高效的办法。
思路
我们可以从方法一的思想开始想,方法一,也就是二分答案,是通过二分区间长度来求解的。那我们可不可以根本不用枚举区间长度,或者说可以快速算出区间长度,然后直接枚举左端点求解。那既然是通过枚举左端点 ,那我们就得思考左端点为 和左端点为 和的答案有什么联系。显然, 每扩大 的范围,就能够覆盖一个数,如果这个数被覆盖,那这个区间就可以再放出一个和这个数值相同的数,也就是右端点 可以缩小范围。在每次扩大 的范围,都更新最短删除区间的长度就行啦!
代码
#include<bits/stdc++.h> using namespace std; const int maxn=1e5+5; int n,a[maxn],ans=1e9; bool vis[maxn];//记录每个数字是否出现再原序列中(没被删除) int main() { ios::sync_with_stdio(0); cin.tie(0); cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; int l=1,r=n; while(l<=n&&!vis[a[l]]) vis[a[l++]]=true; while(r&&!vis[a[r]]) vis[a[r--]]=true; //初始化l,r while(l) { ans=min(ans,r-l+1);//更新答案 vis[a[--l]]=false;//扩大左边界l while(r&&!vis[a[r]]) vis[a[r--]]=true;//缩小有边界r } cout<<n-ans; return 0; } -
- 1
信息
- ID
- 12135
- 时间
- 3000ms
- 内存
- 512MiB
- 难度
- 3
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者