双指针具有单调性
title: 双指针具有单调性
categories:
- ICPC
tags:
- null
abbrlink: 631fa1fb
date: 2024-09-28 00:00:00
双指针的题目往往是看起来需要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版本的唯一区别是$a_i$保证是正整数!
小红拿到了一个数组,她想知道,有多少非空区间满足区间所有元素之和不小于$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;
}
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 爱飞鱼的blog!