尺取法入门习题
尺取法:
一:使用前提
所要求的均是求某个区间来满足试题中给定的条件,而枚举区间的左右端点,可使用两重循环来实现,形如下
for (int left=1;left<n;left++)
for (int right=left,right<=n,right++)
检测区间是否满足条件,如果满足则方案数累加即可
但如果发现找到一个区间[left,right]是满足条件的,而[left+1,right]也满足条件的话,则完全不必按上述两重循环来写。
因为那样的话,存在大量的冗余运算。
二:使用方法
step1:初始化head=1,tail=0. 其中head代表区间的左端点,tail代表区间的右端点
step2:固定好左端点,让右端点一步一步向右移动
step3:调节左端点,找到一个合法的区间,并统计结果。
最后:一定在脑子里形像化的想一下尺取法的运行过程。。。。。。
T1:Diamond Collector钻石收集加强版
贝西是一只牛,她喜欢闪闪发光的东西,于是有在空闲时间里挖钻石的爱好。
她收集了N颗不同大小的钻石(N<=5*10^5),她想在牛棚的展示箱里放一些钻石。因为贝西希望展示在箱子里的钻石大小尽量相似,她决定不会把两颗大 小相差超过K的钻石一起放进箱子里(如果两颗钻石的大小恰好相差K那它们也能一起在箱子里展示)。
给出K,请帮助贝西求出她能展示在箱子里的钻石数量的最大值。
输入格式
输入的第一行包含N和K(0<=K<=500000)。
接下来的N行每行包含一个整数表示其中一颗钻石的大小。所有的大小都是正数且不超过10,000
输出格式
输出一个正整数,表示贝西能展示的钻石数量的最大值
样例
输入数据 1
5 3
1
6
4
3
1
输出数据 1
4
#include<bits/stdc++.h>
using namespace std;
int a[501000];
int main()
{
int n,k,ans;
cin>>n>>k;
for(int i=1;i<=n;i++)
cin>>a[i];
sort(a+1,a+n+1);
int head=1,tail=0;
//初始化操作
ans=0;
while(tail<n)
//设置循环退出条件
{
tail++;
//让右端点一步步右移
while(a[tail]-a[head]>k)
//调节左端点,找到一个合法的区间
head++;
if(tail-head+1>ans)
ans=tail-head+1;
}
cout<<ans<<endl;
return 0;
}
下面这个枚举头结点,让尾结点跑啊跑
#include<bits/stdc++.h> using namespace std; int a[501000]; int main() { int n,k,ans; cin>>n>>k; for(int i=1;i<=n;i++) cin>>a[i]; sort(a+1,a+n+1); int head=1,tail=0; ans=0; int sum=0; for (int head=1;head<=n;head++) { while(tail+1<=n&&a[tail+1]-a[head]<=k) tail++; ans=max(ans,tail-head+1); } cout<<ans<<endl; }
完全平方数之和弱化版
给你一个数字N 问可否分解成若干个连续数字的平方和。
Format
Input
给出数字N,1<=N<=1e14
Output
输出有多少种拆分方案
Samples
输入数据 1
2030
输出数据 1
2
hint
2030=21^2+ 22^2+ 23^2+ 24^2
2030=25^2+ 26^2+ 27^2
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
long long right=0,left=1,cnt=0;
//初始化
long long sum=0,n;
cin>>n;
while(right*right<n)
//设置循环退出条件
{
right++;
//右端点一步步移动
sum+=right*right;
//进行值的累加
while(sum>n)
//调节左端点
{
sum=sum-left*left;
left++;
}
if (sum==n)
//如果能找到一个合法的区间
cnt++;
}
cout<<cnt<<endl;
return 0;
}
枚举左点,让右点跑啊跑。。。。
#include<iostream> #include<algorithm> using namespace std; int main() { long long right=0,left=1,cnt=0; //初始化 long long sum=0,n; cin>>n; for(long long left=1;;left++) //设置循环退出条件 { if ((right+1)*(right+1)>n) break; while(sum+(right+1)*(right+1)<=n) sum=sum+(right+1)*(right+1),right++; if (sum==n) cnt++; sum=sum-left*left; } cout<<cnt<<endl; }
小J读书
你有n本书,读第i本书需要a_i分钟,你现在有m分钟 你读书的方式是这样的:
找出任意一个数x,并按编号从小到大的顺序读第x∼n本 如果你读完了第n本,那么读书结束 如果你准备要读第i本书,但是剩下的时间小于a_i (也就是读不完这本书):那么你就不可以开始读这本书了,并且读书结束
换句话说,如果你读一本书,就必须要读完,否则就不能读。
求最多能读多少本书。
Format
Input
第一行两个整数n,m,分别表示书的数量和限定时间
第二行n个整数a1∼an,表示读每本书的所耗时间
1≤n≤10^5
1≤m≤10^9
1≤ai≤10^4
Output
一行一个整数,表示最多能读的书的数量
Samples
输入数据 1
4 5
3 1 2 1
输出数据 1
3
#include<bits/stdc++.h>
using namespace std;
long long a[100001],s[100001];
int main() {
int n,m;
cin>>n>>m;
s[0]=0;
for(int i=1; i<=n; i++)
{
cin>>a[i];
s[i]=s[i-1]+a[i];
}
int head=1,tail=0,ans=0;
while(tail<n)
{
tail++;
while(head<=tail&&s[tail]-s[head-1]>m)
head++;
ans=max(ans,tail-head+1);
}
cout<<ans<<endl;
return 0;
}
朋友聚会
小J有n个朋友,每个朋友有一定的热情值和工资。为了避免收入带来的一些不和谐因素,所以小J邀请的朋友中两两工资差小于等于d。现在给出朋友的信息,请求出最大热情值是多少。
Format
Input
第一行是n和d,有n个朋友,以及工资差值d。
接下来n行给定n个朋友的信息 第i行表示他的工资为m_i,热情值为s_i。
1<=N<=10^6
1<=d<=10^9
0<=Mi,Si<=10^9
Output
一个整数,满足要求的前提下可以获得的最大热情值
Samples
输入数据 1
4 5
75 5
0 100
150 20
75 1
输出数据 1
100
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int MaxN=1000005;
int n,d,l,r;
long long ans,s[MaxN];
struct node
{
int s,m;
//s代表热情值
//m代表工资
}a[MaxN];
node aa;
bool cmpare(node p,node q)
{
return p.m<q.m;
//<代表从小到大....
}
int main()
{
scanf("%d%d",&n,&d);
for(int i=1;i<=n;i++)
scanf("%d%d",&a[i].m,&a[i].s);
sort(a+1,a+1+n,cmpare);
for(int i=1;i<=n;i++)
s[i]=s[i-1]+a[i].s;
l=1,r=0;
while(r<n)
{
r++;
while(l<=r&&a[r].m-a[l].m>d)
l++;
ans=max(ans,s[r]-s[l-1]);
}
printf("%lld",ans);
return 0;
}
博览购票
博览馆正在展出由世上最佳的M位画家所画的图画。人们想到博览馆去看这几位大师的作品 。可是,那里的博览馆有一个很奇怪的规定,就是在购买门票时必须说明两个数字,a和b, 代表要看展览中的第a幅至第b幅画(包含a和b)之间的所有图画,而门票的价钱就是一张图画 一元。人们希望入场后可以看到所有名师的图画(至少各一张)。可是又想节省金钱……请你写一个程序决定购买门票时的a值和b值。
Format
Input
第一行是N和M,分别代表博览馆内的图画总数及这些图画是由多少位名师的画所绘画的。 其后的一行包含N个数字,它们都介于1和M之间,代表该位名师的编号。 N<=1000000,M<=2000
Output
a和b(a<=b)由一个空格符所隔开。 保证有解,如果多解,输出a最小的。
Samples
输入数据 1
12 5
2 5 3 1 3 2 4 1 1 5 4 3
输出数据 1
2 7
#include<bits/stdc++.h>
using namespace std;
int n,m,a[1000005];
int sum[2005],num=0;
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i];
int l=1,r=0,ans1=INT_MAX,ans2=INT_MAX,qian=INT_MAX;
//ans1记结果区间的左点
//ans2记结果区间的右点、
//qian代表结果区间的长度
while(r<n)
//设置循环退出条件
{
r++;
if(++sum[a[r]]==1)
//如果是第一次出现的数字
num++;
while(num==m&&l<=r&&sum[a[l]]!=1)
//先进行区间的调整,让它在满足条件的前提下,尽可能的短
sum[a[l]]--,l++;
if(num==m&&r-l+1<qian)
//调节结束后,再来统计最优解
{
qian=r-l+1;
ans1=l;
ans2=r;
}
}
cout<<ans1<<' '<<ans2;
return 0;
}
生日礼物
小西有一条很长的彩带,彩带上挂着各式各样的彩珠。已知彩珠有N个,分为K种。简单的说,可以将彩带考虑为x轴,每一个彩珠有一个对应的坐标(即位置)。某些坐标上可以没有彩珠,但多个彩珠也可以出现在同一个位置上。
小布生日快到了,于是小西打算剪一段彩带送给小布。为了让礼物彩带足够漂亮,小西希望这一段彩带中能包含所有种类的彩珠。同时,为了方便,小西希望这段彩带尽可能短,你能帮助小西计算这个最短的长度么?彩带的长度即为彩带开始位置到结束位置的位置差。
Format
Input
第一行包含两个整数N, K,分别表示彩珠的总数以及种类数。
接下来K行,每行第一个数为Ti,表示第i种彩珠的数目。
接下来按升序给出Ti个非负整数,为这Ti个彩珠分别出现的位置。
1≤N≤1000000
1≤K≤60
0≤Ti<2^31
Output
输出应包含一行,为最短彩带长度。
Samples
输入数据 1
6 3
1 5
2 1 7
3 1 3 8
输出数据 1
3
#include<bits/stdc++.h>
using namespace std;
const int M=1000010;
struct oll{
int x,y;
}s[M];
bool cmp(oll a,oll b)
{
return a.x<b.x;
}
int num[M],n,k,t,a,l=1,ans=INT_MAX,sum;
int main(){
cin>>n>>k;
for(int i=1;i<=k;i++)
{
cin>>t;
while(t--)
{
a++ ;
cin>>s[a].x;
s[a].y=i;
}
}
sort(s+1,s+n+1,cmp);
for(int i=1;i<=n;i++)
{//右端点
if(++num[s[i].y]==1)sum++;
while(l<=n&&sum==k){
if(num[s[l].y]==1)break;
num[s[l].y]--;
l++;
}
if(sum==k)ans=min(ans,s[i].x-s[l].x);
}
cout<<ans;
return 0;
}