双指针具有单调性

双指针的题目往往是看起来需要O(n),我们一般枚举一个指针,然后我们发现另一个指针不走回头路,不论是哪个方向,这样我们的时间复杂度就是O(n).
从例题来看:

给定一个字符串,我们希望找到最短长度区间能包含所有字母类型。

核心:对于左端点固定的时候,我们找到最小的r,然后我们考虑i右移动一位,这时候我们的j是一定不会回头的,因为不回头,都已经少了一个字母且当前假设已经不包含所有字母了,。

所以i和j都是单调移动的
https://codeforces.com/contest/701/problem/C

// Problem: C. They Are Everywhere // Contest: Codeforces - Codeforces Round 364 (Div. 2) // URL: https://codeforces.com/problemset/problem/701/C // Memory Limit: 256 MB // Time Limit: 2000 ms // // Powered by CP Editor (https://cpeditor.org) #include <bits/stdc++.h> using namespace std; #define ll long long //# define int long long #define ull unsigned long long #define pii pair<int,int> #define baoliu(x, y) cout << fixed << setprecision(y) << x #define endl "\n" #define debug1(x) cerr<<x<<" " #define debug2(x) cerr<<x<<endl const int N = 2e5 + 10; const int M = 1e6 + 10; const int inf = 0x3f3f3f3f; const int mod = 998244353; const double eps = 1e-8; int n, m; int a[N]; void solve(){ cin>>n; string str;cin>>str; set<int>s; for(int i=0;i<n;i++){ s.insert(str[i]); } int m=s.size(); map<char,int >ans;int res=inf; int j=0; for(int i=0;i<n;i++){ while(j<n&&ans.size()<m){ ans[str[j]]++; j++; } //cerr<<i<<" "<<j<<endl; if(ans.size()==m)res=min(res,j-i); ans[str[i]]--; if(ans[str[i]]==0)ans.erase(str[i]); } cout<<res; } int main() { cin.tie(0); ios::sync_with_stdio(false); int t; //cin>>t; t=1; while (t--) { solve(); } return 0; }

例题2

https://ac.nowcoder.com/acm/problem/269221

题意:需要在有序数组中找到最长区间满足区间极差小于等于m

做法:我们考虑枚举左端点,那么当左端点增大,右端点一定只能不动或者向右移动

  • 所以也可以双指针解决
void solve(){ cin>>n>>m; for(int i=1;i<=n;i++)cin>>a[i]; int ans=-1; sort(a+1,a+n+1);int j=1; //for(int i=1;i<=n;i++)cerr<<a[i]<<" "; //cerr<<endl; for(int i=1;i<=n;i++){ while(j+1<=n&&a[j+1]-a[i]<=m)j++; //cerr<<i<<" "<<j<<endl; ans=max(ans,j-i+1); } double res=1.0*ans/(double)n; baoliu(res,6); }

本题为easy版本,和hard版本的唯一区别是ai保证是正整数****!

小红拿到了一个数组,她想知道,有多少非空区间满足区间所有元素之和不小于k

由于这个版本a[i]都是正整数,我们直接前缀和转化以后,等价于前缀和数组视角下的上一题

void solve(){ int k;cin>>n>>k; for(int i=1;i<=n;i++)cin>>a[i]; int j=1; int ans=0; for(int i=1;i<=n;i++)s[i]=s[i-1]+a[i]; for(int i=1;i<=n;i++){ while(j<=n&&s[j]-s[i-1]<k)j++; //cerr<<i<<" "<<j<<endl; ans+=n-j+1; } cout<<ans; }

hard版本:带负数情况

  • 我们需要找到大于a[i]+k的数a[j]且j>i.
  • 由于存在负环没有单调性,我们只能用解决逆序对的方法:离散化权值树状数组
  • 我们要求的是后缀,所以还要倒着求,我们枚举的是左端点

离散化过程明显不熟,下次看到的时候一定要刻意练习

// Problem: 小红统计区间(hard) // Contest: NowCoder // URL: https://ac.nowcoder.com/acm/contest/73239/F // Memory Limit: 524288 MB // Time Limit: 2000 ms // // Powered by CP Editor (https://cpeditor.org) #include <bits/stdc++.h> using namespace std; #define ll long long # define int long long #define ull unsigned long long #define pii pair<int,int> #define baoliu(x, y) cout << fixed << setprecision(y) << x #define endl "\n" #define debug1(x) cerr<<x<<" " #define debug2(x) cerr<<x<<endl const int N = 2e5 + 10; const int M = 1e6 + 10; const int inf = 0x3f3f3f3f; const int mod = 998244353; const double eps = 1e-8; int n, m; int a[N]; vector<int>lan; template <typename T> struct Fenwick { int n; std::vector<T> a; Fenwick(int n_ = 0) { init(n_); } void init(int n_) { n = n_; a.assign(n, T{}); /* T{}是一种初始化T类型对象的方式,它使用了花括号初始化列表进行数值初始化。 对于大多数内置数据类型(如整数、浮点数),这将初始化为0。 */ } void add(int x, const T &v) { for (int i = x; i <=n-1; i += i & -i) { a[i] = a[i] + v; } } T sum(int x) { T ans{}; for (int i = x; i > 0; i -= i & -i) { ans = ans + a[i]; } return ans; } T rangeSum(int l, int r) {//要传入l-1 //待优化:直接给个vector记录前缀和 return sum(r) - sum(l); } int select(const T &k) {//寻找最后一个使得前缀和小于等于 k 的位置。 int x = 0; T cur{}; for (int i = 1 << std::__lg(n-1); i; i /= 2) {//GCC if (x + i <= n-1 && cur + a[x + i] <= k) { x += i; cur = cur + a[x]; } } return x; } }; void discrete() { sort(lan.begin(), lan.end()); //排序 lan.erase(unique(lan.begin(), lan.end()), lan.end()); //去重 } //查询 int find(int x) { return lower_bound(lan.begin(), lan.end(), x)-lan.begin()+1; //返回所查询的数据的下标 } //为了迎合这大于等于第一个数的离散数,我们希望我们在查询的时候 //能得到查找一系列大于什么的数,s[j]>=s[i]+k //老是糊涂的一点是,我们枚举的l-1,s[r]-s[l-1]; //所以我们枚举的时候是0-n-1,我们需要不断查询后缀和,如果正着做 //我们会不知道后面到底有多少个数,因为离散化vector只有大小关系 //原来的位置关系并不知道。所以我们从后往前扫,首先记得将s[n]加入到 //树状数组中去,因为枚举的时候是0-n-1 void solve(){ int k;cin>>n>>k; for(int i=1;i<=n;i++){ cin>>a[i]; a[i]+=a[i-1]; lan.push_back(a[i]); } Fenwick<int>c(n+1); discrete(); //for(auto x:lan)cerr<<x<<" "; //cerr<<endl; int ans=0; int tt=find(a[n]); c.add(tt,1); for(int i=n-1;i>=0;i--){ int pos=1; pos=find(a[i]+k); // cerr<<a[i]<<" "<<a[i]+k<<" "<<pos<<endl; if(pos!=lan.size()+1)ans+=c.rangeSum(pos-1,n); //cerr<<c.rangeSum(pos-1,n)<<endl; int tmp=find(a[i]); c.add(tmp,1); } cout<<ans<<endl; } signed main() { cin.tie(0); ios::sync_with_stdio(false); int t; //cin>>t; t=1; while (t--) { solve(); } return 0; }

__EOF__

本文作者爱飞鱼
本文链接https://www.cnblogs.com/mathiter/p/18064963.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   potential-star  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示