尺取法
尺取法
应用背景:
(1)给定一个序列,有时需要它是有序的,先排序
(2)问题和序列的区间有关,且需要操作两个变量,可以用两个下标(指针)i和j扫描区间
应用 + 例题
综合运用
双指针找合法区间
Qiansui_code
寻找区间和
利用双指针寻找区间和
洛谷 P1147 连续自然数和
双指针简单题,模拟题意即可,错了的话就是等号啥的写烂了,下标啥的写错了
Qiansui_code
poj 3061 Subsequence
利用i和j标识滑动窗口的前后位置,因为当前ij段若大于s,则删头;若当前ij段小于s,则应继续加尾。
例:
//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
//#define int long long
typedef std::pair<int,int> pii;
typedef std::pair<ll,ll> pll;
typedef std::pair<ull,ull> pull;
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
return x*f;
}
using namespace std;
const int maxm=1e5+5,inf=0x3f3f3f3f,mod=998244353;
ll n,s,a[maxm];
void solve(){
cin>>n>>s;
for(int i=1;i<=n;++i){
cin>>a[i];
}
bool f=false;
ll sum=a[1],ans=n+5;
for(int i=1,j=1;i<=n&&j<=n;){
if(sum>=s){
if(ans>j-i+1){//统计答案
ans=j-i+1;
f=true;
}
sum-=a[i];
++i;
if(i>j){
sum=a[i];
++j;
}
}
if(sum<s){
++j;
sum+=a[j];
}
}
if(f) cout<<ans<<'\n';
else cout<<0<<'\n';
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _=1;
cin>>_;
while(_--){
solve();
}
return 0;
}
poj 2566 Bound Found
与上面的题不同,这里需要利用前缀和转换一下,利用前缀和将滑动窗口显现出来,再利用尺取法找与题给的数字t差的绝对值最小的连续子序列
摘记:古怪的编译器。。。还得了解了解poj的c++和g++的区别
//>>>Qiansui
//g++过的
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
//#define int long long
// typedef pair<int,int> pii;
// typedef pair<ll,ll> pll;
// typedef pair<ull,ull> pull;
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
return x*f;
}
using namespace std;
const int maxm=1e5+5,inf=0x3f3f3f3f,mod=998244353;
ll n,k,a[maxm],t;
pair<ll,int> sum[maxm];
void solve(){
while(1){
cin>>n>>k;
if(n==0) break;
sum[0]=make_pair(0,0);
for(int i=1;i<=n;++i){
cin>>a[i];
sum[i]=make_pair(sum[i-1].first+a[i],i);
}
sort(sum,sum+1+n);//注意从0开始,因为前缀和
for(int z=0;z<k;++z){
cin>>t;
ll ans,l,r,cha=inf,ss;
for(int i=0,j=1;j<=n&&i<=n;){
ss=sum[j].first-sum[i].first;
if(llabs(ss-t)<=cha){//找到一个更小的
ans=ss;
cha=llabs(ss-t);
l=sum[i].second;
r=sum[j].second;
}
if(ss>t) ++i;
else if(ss<t) ++j;
else break;
if(i==j) ++j;
}
if(r<l) swap(l,r);//保证顺序,左在l,右在r
cout<<ans<<' '<<l+1<<' '<<r<<'\n';//左区间需要加一
}
}
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _=1;
// cin>>_;
while(_--){
solve();
}
return 0;
}
多指针
洛谷 P1102 A-B 数对
有时候两个窗口指针不够用,需要更多的指针
思路:对于给定数列进行排序,定义三个指针 i, j, k,遍历 i 计算每次 i 对于答案的贡献,区间 [j,k] 为满足条件 \(A - B = C\) 的相同数 A 的下标区间,j, k 随着 i 的增大而增大
qiansui_code
hdu 5358 First One
题意
给你一个长为 n 的序列,定义 \(S(i,j)\) 为序列下标区间 [i,j] 的和。
求解式子:$\displaystyle \sum_{i = 1}^{n} \sum_{j = i}^{n} (\lfloor \log_2 S(i, j) \rfloor + 1) \times (i + j) $
注:本题中定义 $\log_2 0 = 0 $;$1 \le n \le 10^5 $
思路
参考思路 传送门
本题不能应用 \(O(n^2)\) 的做法,顶天是 \(O(n \log n)\) 的做法,因为枚举每个区间的办法不可行
观察可知式子 $\lfloor \log_2 S(i, j) \rfloor + 1 $ 代表的是数 \(S(i,j)\) 的二进制表示的位数,那么由于 \(S(i,j) \le 10 ^ {10},\lfloor \log_2 S_{max}(i, j) \rfloor + 1 = 34\),所以本题中区间和的二进制位数最多只有 34 种,我们可以对区间和的二进制位数进行枚举,再遍历每一个下标为起点,在其右侧利用尺取法找到其满足条件的右下标区间,累加答案即可。时间复杂度 \(O(34 \times n)\)
代码
//>>>Qiansui
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x, y, sizeof(x))
#define debug(x) cout << #x << " = " << x << '\n'
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << '\n'
//#define int long long
using namespace std;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<ull, ull> pull;
typedef pair<double, double> pdd;
/*
*/
const int maxm = 1e5 + 5, inf = 0x3f3f3f3f, mod = 998244353, N = 36;
ll a[maxm];
void solve(){
ll n, ans = 0;
cin >> n;
vector<ll> b(n + 1, 0);
for(int i = 1; i <= n; ++ i){
cin >> a[i];
b[i] = b[i - 1] + a[i];
}
ll l = 0, r = 1;
for(int i = 1; i < N; ++ i){
if(b[n] < l) break;
ll x = 1, y = 0, num = 0;
for(int j = 1; j <= n; ++ j){
x = x > j ? x : j;
while(x <= n && b[x] - b[j - 1] < l) ++ x;
y = y > x - 1 ? y : x - 1;
while(y + 1 <= n && b[y + 1] - b[j - 1] >= l && b[y + 1] - b[j - 1] <= r)
++ y;
//区间[x,y] 即为满足条件的右下标区间
//下面的式子即为累加:(j + x) + (j + x + 1) + ... + (j + y)
if(x <= y)
num += (y - x + 1) * j + (y + x) * (y - x + 1) / 2;
}
ans += num * i;
l = 1ll << i; r = (l << 1) - 1;
}
cout << ans << '\n';
return ;
}
signed main(){
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
int _ = 1;
cin >> _;
while(_ --){
solve();
}
return 0;
}
求包含多个集合元素的最优解
uva 11572 Unique Snowflakes
求尽量长的一个连续子序列满足其没有重复的元素
// key code
void solve(){
int n, ans = 0;
cin >> n;
vector<int> a(n);
for(int i = 0; i < n; ++ i){
cin >> a[i];
}
map<int, int> q;
for(int i = 0, j = 0; i < n && j < n;){
while(j < n && q[a[j]] == 0){
++ q[a[j]];
++ j;
}
ans = max(ans, j - i);
-- q[a[i]]; ++ i;
}
cout << ans << '\n';
return ;
}