例题
数的范围
输入样例:
6 3
1 2 2 3 3 4
3
4
5
输出样例:
3 4
5 5
-1 -1
思路:这是一个练习二分的好问题,他通过改变更新条件实现找一个数的左边界和右边界,本质上还是二分的模板
代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
const int N=100003;
int a[N];
int main()
{
int n,q;
cin>>n>>q;
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
int l,r,mid,t;
while(q--)
{
scanf("%d",&t);
l=1,r=n;
while(l<r)
{
mid=l+r>>1;
if(a[mid]>=t) r=mid;
else l=mid+1;
}
if(a[l]==t)
printf("%d ",l-1);
else
{
puts("-1 -1");
continue;
}
l=1,r=n;
while(l<r)
{
mid=l+r+1>>1;
if(a[mid]<=t) l=mid;
else r=mid-1;
}
printf("%d\n",l-1);
}
return 0;
}
数的三次方根
这是一个关于浮点型二分的问题,我们需要注意的一个问题就是边界的大小,如果不太确定边界的话就尽量使边界大一点,还有一个问题就是精度问题,不能太小,一般1e-7即可
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
int main()
{
double n;
scanf("%lf",&n);
double mid,l=-100,r=100;
while(r-l>0.00000001)
{
mid=(l+r)/2;
if(mid*mid*mid<n) l=mid;
else r=mid;
}
printf("%lf",l);
return 0;
}
前缀和
这是一个前缀和的模板,必须要熟练理解并掌握
代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
const int N=100003;
int a[N],sum[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
sum[i]=sum[i-1]+a[i];
}
int l,r;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&l,&r);
printf("%d\n",sum[r]-sum[l-1]);
}
return 0;
}
子矩阵的和
输入样例:
3 4 3
1 7 2 4
3 6 2 8
2 1 2 3
1 1 2 2
2 1 3 4
1 3 3 4
输出样例:
17
27
21
这是二维前缀和的模板,必须要熟练掌握,最好画个图理解一下
代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
const int N=1003;
int sum[N][N];
int main()
{
int n,m,q;
cin>>n>>m>>q;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
scanf("%d",&sum[i][j]);
sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
}
int x1,y1,x2,y2;
while(q--)
{
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
printf("%d\n",sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1]);
}
return 0;
}
习题
机器人跳跃问题
输入样例1:
5
3 4 3 2 4
输出样例1:
4
输入样例2:
3
4 4 4
输出样例2:
4
输入样例3:
3
1 6 4
输出样例3:
3
这是一道比较有技巧性的二分题目,首先不难看出这道题目可以用二分来做,因为每次对于能量E而言,他度过h[i]的建筑时能量会变为2*E-h[i],也就是如果答案为ans,那么比ans大的所有数都能够平安度过,所以答案具有单调性,我们可以用二分来解决,但是有一个特别需要注意的点就是2*E-h[i]在极端情况下是一个指数增长的表达式,根本就存不了,但是我们需要发现一个特点,就是当E大于之后建筑的最大值了那么一定能平安通过,这个时候可以来个剪枝,这样就能够ac了
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
const int N=100003;
long long h[N];
int n;
bool check(long long x)
{
for(int i=1;i<=n;i++)
{
x+=x-h[i];
if(x>=1e5) return true;//非常重要的剪枝
if(x<0) return false;
}
return true;
}
int main()
{
cin>>n;
long long l=0,r=0,mid;//此处要开long long,否则在check函数中会溢出
for(int i=1;i<=n;i++)
{
scanf("%d",&h[i]);
r=max(r,h[i]);//所有建筑的最大值一定满足题意
}
while(l<r)
{
mid=l+r>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
printf("%lld",l);
return 0;
}
四平方和
输入样例:
5
输出样例:
0 0 1 2
对于这道题我们肯定不能直接暴力去做,o(n^4)肯定是会超时的,我们可以先将两个数所能组成的平方和先存下来,看看所给的数能否用我们所存储的两个数表示即可,这样复杂度就变为了o(n^2)
代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<map>
#include<cmath>
using namespace std;
int main()
{
int num;
cin>>num;
int n=sqrt(num);
map<int,int>m;
for(int i=0;i<=n;i++)
for(int j=i;j<=n;j++)
m[i*i+j*j]=1;
for(int i=0;i<=n;i++)
for(int j=i;j<=n;j++)
{
if(m[num-i*i-j*j]!=1) continue;
for(int k=j;k<=n;k++)
{
double temp=sqrt(num-i*i-j*j-k*k);
if(temp==(int)temp)
{
cout<<i<<" "<<j<<" "<<k<<" "<<(int)temp;
return 0;
}
}
}
return 0;
}
分巧克力
输入样例:
2 10
6 5
5 6
输出样例:
2
如果边长为a的巧克力可以满足题意,则边长为a-1的巧克力更可以满足题意,所以巧克力的边长具有单调性,我们可以用二分来做,还有一点就是注意理解一块长为a,宽为b的巧克力最多可以分成(a/c)*(b/c)块边长为c的巧克力,知道了这个之后就可以写一个复杂度为o(n)的check函数,详情见代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=100003;
int a[N],b[N];
int main()
{
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++)
scanf("%d%d",&a[i],&b[i]);
int l=1,mid,r=1000000000;
while(l<r)
{
mid=l+r+1>>1;
int cnt=0;
for(int i=1;i<=n;i++)
cnt+=((a[i]/mid)*(b[i]/mid));
if(cnt>=k) l=mid;
else r=mid-1;
}
printf("%d",l);
return 0;
}
激光炸弹
输入样例:
2 1
0 0 1
1 1 1
输出样例:
1
这是一个比较基础的二维前缀和问题,需要注意的就是边界问题以及特殊情况,边界问题就是我们在处理二维前缀和问题时最好使数组下标从1开始避免一些不必要的麻烦,特殊情况就是当r大于5001时,为了能适用于我们的代码,我们可以将其赋值为5001,不影响答案,也不用修改我们之前写过的代码
代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
const int N=5003;
int s[N][N];
int main()
{
int n,r;
cin>>n>>r;
if(r>5001) r=5001;//将非常规数据转换为常规数据
int x,y,w;
for(int i=1;i<=n;i++)
{
scanf("%d%d%d",&x,&y,&w);
x++;y++;//注意下标从1开始方便运算
s[x][y]+=w;//同一个位置可能有多个物品
}
for(int i=1;i<=5001;i++)
for(int j=1;j<=5001;j++)
s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
int ans=0;
for(int i=r;i<=5001;i++)
for(int j=r;j<=5001;j++)
ans=max(ans,s[i][j]-s[i-r][j]-s[i][j-r]+s[i-r][j-r]);
printf("%d",ans);
return 0;
}
K倍区间
区间[l,r]时k倍区间当且仅当(sum[r]-sum[l-1])%k=0,也就是sum[r]和sum[l-1]模k所得余数相同,那么我们就可以预处理出来前缀和对k的余数,然后从前往后遍历,假如说遍历到第i个数,有sum[i]%k=p,看看这个数之前有多少数的前缀和对k取余也为p,就计入答案,知道遍历完整个前缀和数组即可
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=100003;
int a[N];//记录前缀和
int sum[N];//sum[i]记录遍历到当前位置对k取余得到值为i的区间有多少个
int main()
{
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
a[i]=(a[i]+a[i-1])%k;
}
long long ans=0;//注意此处要开longlong防止极端情况,类似于100000个数全为0
sum[0]=1;//考虑到整个前缀和是k的倍数的情况
for(int i=1;i<=n;i++)
{
ans+=sum[a[i]];
sum[a[i]]++;
}
printf("%lld",ans);
return 0;
}