尺取法

尺取法

应用背景:
(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 ;
}

2023杭电多校第四场 - 3 Simple Set Problem

2023牛客多校第五场 - G Go to Play Maimai DX

posted on 2023-07-27 23:28  Qiansui  阅读(12)  评论(0编辑  收藏  举报