双指针Level2

这是第二讲,可以看看这篇文章:https://codeforces.com/edu/course/2/lesson/9/2

这次主要涉及的是维护一个区间的性质的问题。


A

题意

给你一个数列,你需要找出最长的连续一段,并且这一段的和不能超过\(k\)

方法

我们可以使用两个双指针进行维护,每一次移动完右指针后,判断当前这个区间是否满足条件,如果不满足,则左指针右移动,知道满足即可,再更新区间和。

代码

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
void solve()
{
int n; cin>>n;
ll s; cin>>s;
std::vector<ll> a(n,0);
for(int i=0;i<n;i++) cin>>a[i];
ll l=0,r;
ll sum=0;
ll ans=0;
for(r=0;r<n;r++)
{
sum+=a[r];
while(sum>s)
{
sum-=a[l];
l++;
}
ans=max(ans,r-l+1);
}
cout<<ans<<"\n";
}
int main()
{
int t=1;
//cin>>t;
while(t--) solve();
return 0;
}

B

题意

给你一个数列,你需要找出最短的连续一段,并且这一段的和不能小于\(k\)

方法

还是跟上面的方法类似,我们这里只需要不断缩小区间即可。注意最后更新答案时需要特判一下。

代码

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
void solve()
{
int n; cin>>n;
ll s; cin>>s;
std::vector<ll> a(n,0);
for(int i=0;i<n;i++) cin>>a[i];
ll l=0,r;
ll sum=0;
ll ans=1e9;
for(r=0;r<n;r++)
{
sum+=a[r];
while(sum-a[l]>=s)
{
sum-=a[l];
l++;
}
if(sum>=s) ans=min(ans,r-l+1); //可能sum本身小于s
}
if(ans==1e9) cout<<-1<<"\n";
else cout<<ans<<"\n";
}
int main()
{
int t=1;
//cin>>t;
while(t--) solve();
return 0;
}

C

题意

给你一个数列,你需要找出:连续一段的和不超过\(k\)的子数列个数。

方法

题意与A基本一致,只不过这里找的是个数。我们可以发现,只要一个子数列满足,那么该子数列的其中一段也必然满足,所以我们每次只需要找到最长的即可。

代码

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
void solve()
{
int n; cin>>n;
ll s; cin>>s;
std::vector<ll> a(n,0);
for(int i=0;i<n;i++) cin>>a[i];
ll l=0,r;
ll sum=0;
ll ans=0;
for(r=0;r<n;r++)
{
sum+=a[r];
while(sum>s)
{
sum-=a[l];
l++;
}
ans+=r-l+1; //最长是r-l+1 比这小的都满足 一共有r-l+1个
}
cout<<ans<<"\n";
}
int main()
{
int t=1;
//cin>>t;
while(t--) solve();
return 0;
}

D

题意

给你一个数列,你需要找出:连续一段的和不能小于\(k\)的个数。

方法

方法还是一样的,我们可以发现,如果这一段子数列满足,那么任何完整包含它的数列都是满足的。我们还是可以采取类似的方法:找到最短的,计算前面左指针的个数即可。同样需要判断一下当前这一段是否符合条件。

代码

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
void solve()
{
int n; cin>>n;
ll s; cin>>s;
std::vector<ll> a(n,0);
for(int i=0;i<n;i++) cin>>a[i];
ll l=0,r;
ll sum=0;
ll ans=0;
for(r=0;r<n;r++)
{
sum+=a[r];
while(sum-a[l]>=s)
{
sum-=a[l];
l++;
}
ans+=l;
if(sum>=s) ans++;
}
cout<<ans<<"\n";
}
int main()
{
int t=1;
//cin>>t;
while(t--) solve();
return 0;
}

E

题意

给你一个数列,你需要找出:满足连续一段中不同元素的个数不超过\(k\)的子数列的个数。

方法

我们可以用\(map\)来记录一下当前所在的数列拥有的元素,并且记录当前所在数列的不同元素的个数。如果不满足条件,则左指针移动,并且删去对应的元素(更新\(map\))。记录个数可以参考上面的C,道理是一样的。

代码

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
void solve()
{
int n,k; cin>>n>>k;
std::vector<int> a(n,0);
for(int i=0;i<n;i++) cin>>a[i];
std::map<int, int> map;
int l=0,r;
ll num=0;
ll ans=0;
for(r=0;r<n;r++)
{
map[a[r]]++;
if(map[a[r]]==1) num++;
while(num>k)
{
map[a[l]]--;
if(map[a[l]]==0)
{
map.erase(a[l]);
num--;
}
l++;
}
ans+=1ll*(r-l+1);
}
cout<<ans<<"\n";
}
int main()
{
int t=1;
//cin>>t;
while(t--) solve();
return 0;
}

F

题意

给你一个数列,要求找到:满足连续一段中,最大值和最小值的差小于\(k\)的子数列的个数。

方法一

翻译题目就是:\(max(a[l······r]-min(a[l······r])<k)\),我们可以发现:如果这一段缩小,那么他的\(max\)肯定会越来越小,\(min\)会越来越大。那么如果这一段满足题意,那么他的子数列都满足题意。

我们可以使用两种方法:一种是单调栈,一种是将这一段划分为两个\(Stack\)进行维护。(这里的\(Stack\)是自定义的)

先介绍单调栈:我们可以使用单调栈:一个维护当前区间的最大值,另一个维护当前区间的最小值,通过这两个栈维护的值来更新区间。

代码一

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
void solve()
{
int n; cin>>n;
ll k; cin>>k;
std::vector<ll> a(n,0);
for(auto &t: a) cin>>t;
deque<ll> maxn,minn;
ll l=0,r;
ll ans=0;
for(r=0;r<n;r++)
{
while(!maxn.empty() && a[maxn.back()]<a[r]) maxn.pop_back();
while(!minn.empty() && a[minn.back()]>a[r]) minn.pop_back();
maxn.push_back(r),minn.push_back(r);
while(a[maxn.front()]-a[minn.front()]>k)
{
l++;
while(maxn.front()<l) maxn.pop_front(); //注意区间的范围>=l
while(minn.front()<l) minn.pop_front();
}
ans+=r-l+1;
}
cout<<ans<<"\n";
}
int main()
{
int t=1;
//cin>>t;
while(t--) solve();
return 0;
}

方法二

可以将其划分为两个\(Stack\),并且分别维护最大值和最小值(注意这里的push_back,维护的最大或者最小(除了\(stk\)))。

通过分别获取\(max\)\(min\)进行更新。注意以下几点:

  • \(stk\)维护的是实际的值,\(maxn\)或者\(minn\)维护的是我们想要的性质。

代码二

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
struct Stack
{
std::vector<ll> stk,maxn={LONG_LONG_MIN},minn={LONG_LONG_MAX};
void push_back(ll x)
{
stk.push_back(x);
maxn.push_back(std::max(maxn.back(),x));
minn.push_back(std::min(minn.back(),x));
}
ll max()
{
return maxn.back();
}
ll min()
{
return minn.back();
}
ll pop()
{
ll x=stk.back();
stk.pop_back();
maxn.pop_back();
minn.pop_back();
return x;
}
bool empty()
{
return stk.empty();
}
}s1,s2;
void remove()
{
if(s1.empty())
{
while(!s2.empty()) s1.push_back(s2.pop());
}
s1.pop();
}
void solve()
{
int n; cin>>n;
ll k; cin>>k;
std::vector<ll> a(n,0);
for(auto &t: a) cin>>t;
ll l=0,r;
ll ans=0;
for(r=0;r<n;r++)
{
s2.push_back(a[r]);
while(std::max(s1.max(), s2.max())-std::min(s1.min(),s2.min())>k)
{
remove();
l++;
}
ans+=r-l+1;
}
cout<<ans<<"\n";
}
int main()
{
int t=1;
//cin>>t;
while(t--) solve();
return 0;
}

G

题意

给你一个数列,你需要找出其中最短的一段,满足:他们的最小公约数一直为1。

方法

还是可以使用上面的做法,这里我们维护的是最小公约数。

这种方法适用于:我们要对左指针更新后,进行修改时,很难采用普通的方法更新判断。所以可以使用两个栈来进行维护。

代码

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
struct Stack
{
std::vector<ll> stk,val={0};
void push_back(ll x)
{
stk.push_back(x);
val.push_back(gcd(val.back(),x));
}
ll top()
{
return val.back();
}
ll pop()
{
ll x=stk.back();
stk.pop_back();
val.pop_back();
return x;
}
bool empty()
{
return stk.empty();
}
}s1,s2;
void remove()
{
if(s1.empty())
{
while(!s2.empty()) s1.push_back(s2.pop());
}
s1.pop();
}
void solve()
{
int n; cin>>n;
std::vector<ll> a(n,0);
for(auto &t: a) cin>>t;
ll l=0,r;
ll ans=1e9;
for(r=0;r<n;r++)
{
s2.push_back(a[r]);
while(gcd(s1.top(), s2.top())==1)
{
ans=min(ans,r-l+1);
remove();
l++;
}
}
if(ans==1e9) cout<<-1<<"\n";
else cout<<ans<<"\n";
}
int main()
{
int t=1;
//cin>>t;
while(t--) solve();
return 0;
}