目录
①二分
一般如果题目的答案在一个有序区间,且具有两段性,即在它前面的数据满足一个性质,而在它后面的性质也满足一个性质,即可用二分法
数的范围
题目详情:
给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。
对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 0 开始计数)。
如果数组中不存在该元素,则返回
-1 -1
。输入格式
第一行包含整数 n 和 q,表示数组长度和询问个数。
第二行包含 n 个整数(均在 1∼10000 范围内),表示完整数组。
接下来 q 行,每行包含一个整数 k,表示一个询问元素。
输出格式
共 q 行,每行包含两个整数,表示所求元素的起始位置和终止位置。
如果数组中不存在该元素,则返回
-1 -1
。数据范围
1≤n≤100000
1≤q≤10000
1≤k≤10000输入样例:
6 3 1 2 2 3 3 4 3 4 5
输出样例:
3 4 5 5 -1 -1
代码 :
#include<iostream>
#include<algorithm>
using namespace std;
const int N=100010;
int q; //q个查询
int n;
int f[N];
int main()
{
cin>>n>>q;
for(int i=0;i<n;i++) cin>>f[i];
while(q--)
{
int k;
cin>>k;
//先找左边界
int l=0,r=n-1;
while(l<r)
{
int mid=l+r>>1;
if(f[mid]>=k) r=mid;
else l=mid+1;
}
//如果左边界存在,找右边界
if(f[r]==k)
{
cout<<r<<" ";
l=r,r=n-1;
while(l<r)
{
int mid=l+r+1>>1;
if(f[mid]<=k) l=mid;
else r=mid-1;
}
cout<<l<<endl;
}
else
{
cout<<"-1 -1"<<endl;
}
}
return 0;
}
数的三次方根
给定一个浮点数 n,求它的三次方根。
输入格式
共一行,包含一个浮点数 n。
输出格式1e
共一行,包含一个浮点数,表示问题的解。
注意,结果保留 6 位小数。
数据范围
−10000≤n≤10000
输入样例:
1000.00
输出样例:
10.000000
分析 :
注意类型要设置为double型,因为我们在比较l和r的时候要求的精度比较高
float类型和double类型的区别https://www.runoob.com/w3cnote/float-and-double-different.html
代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
int main()
{
double n;
cin>>n;
double l=-10000,r=10000;
while(r-l>1e-8)
{
double mid=(r+l)/2;
if(mid*mid*mid>=n) r=mid;
else l=mid; //这里不能加1,因为这是浮点型数据
}
printf("%.6lf",r);
}
机器人跳跃问题
题目详情:
机器人正在玩一个古老的基于 DOS 的游戏。
游戏中有 N+1座建筑——从 0 到 N 编号,从左到右排列。
编号为 0 的建筑高度为 0 个单位,编号为 i 的建筑高度为 H(i) 个单位。
起初,机器人在编号为 0 的建筑处。
每一步,它跳到下一个(右边)建筑。
假设机器人在第 k 个建筑,且它现在的能量值是 E,下一步它将跳到第 k+1 个建筑。
如果 H(k+1)>E,那么机器人就失去 H(k+1)−E 的能量值,否则它将得到 E−H(k+1) 的能量值。
游戏目标是到达第 N 个建筑,在这个过程中能量值不能为负数个单位。
现在的问题是机器人至少以多少能量值开始游戏,才可以保证成功完成游戏?
输入格式
第一行输入整数 N。
第二行是 N 个空格分隔的整数,H(1),H(2),…,H(N) 代表建筑物的高度。
输出格式
输出一个整数,表示所需的最少单位的初始能量值上取整后的结果。
数据范围
1≤N,H(i)≤10^5,
输入样例1:
5 3 4 3 2 4
输出样例1:
4
输入样例2:
3 4 4 4
输出样例2:
4
输入样例3:
3 1 6 4
输出样例3:
3
分析 :
1、该题的答案一定在1~最大能量塔(或者一个很大的数,如1e8)之间,假如初始能量比最大能量大,不可能有损耗哒!所以我们可以用二分来做
2、分析题可知,无论是失去能量还是得到能量,能量都会变为2E-H(K+1)
3、在判断能量是否足够时,假如当能量已经比当前能量塔中最大的塔的能量都多了,我们就可以返回了,因为此时能量已经不会损耗了,如果一直加下去,数据会溢出造成错误
代码 :
#include<iostream>
#include<algorithm>
using namespace std;
const int N=100010;
int h[N];
int n;
int ans;
int Max_H=-1;
bool success(int u)
{
for(int i=0;i<n;i++)
{
u=2*u-h[i]; //2*u!!!不可以直接写成2u 傻了
if(u<0) return false;
if(u>=Max_H) return true;
}
return true;
}
int main()
{
cin>>n; //n座能量塔
for(int i=0;i<n;i++)
{
cin>>h[i];
Max_H=max(Max_H,h[i]);
}
int l=1,r=1e8; //或者r=Max_H
int mid=0;
while(l<r)
{
mid=l+r>>1;
//假如该值成功,那么我们再往小了找
if(success(mid))
r=mid;
else
l=mid+1;
}
cout<<r;
return 0;
}
四平方和
题目详情:
四平方和定理,又称为拉格朗日定理:
每个正整数都可以表示为至多 4 个正整数的平方和。
如果把 0 包括进去,就正好可以表示为 4 个数的平方和。
比如:
5=0^2+0^2+1^2+2^2
7=1^2+1^2+1^2+2^2对于一个给定的正整数,可能存在多种平方和的表示法。
要求你对 4 个数排序:
0≤a≤b≤c≤d
并对所有的可能表示法按 a,b,c,d为联合主键升序排列,最后输出第一个表示法。
输入格式
输入一个正整数 N。
输出格式
输出4个非负整数,按从小到大排序,中间用空格分开。
数据范围
0<N<5∗10^6
输入样例:
5
输出样例:
0 0 1 2
分析:
1、可以用暴搜三层循环,但是时间复杂度太高
2、为了降低时间复杂度,我们可以先将c^2+d^2的值存下来,再依次枚举a,b
3、那怎么存c^2+d^2呢?可以定义一个结构体,也可以开一个哈希数组
(我的脑子肯定想不出来T~T)
对了,我们可以看一下a b c d的大小关系:
a最大的情况为a=b=c=d
b最大的情况为a=0,b=c=d
c最大的情况为a=b=0,c=d
d最大的情况为a=b=c=0,d*d=n
代码:
哈希数组版(非二分):
数组h[N]全部初始化为0
h[c^2+d^2]=c+1
#include<iostream> #include<iostream> #include<cmath> using namespace std; const int N=10000010; //一定要开大一点!!!! long long h[N]; //哈希数组 int n; //需要求的数 int main() { cin>>n; //先枚举c、d并记录 for(int c=0;c*c<=n/2;c++) for(int d=0;d*d<=n;d++) { if(h[c*c+d*d]==0) h[c*c+d*d]=c+1; //避免c等于0的情况出现 } for(int a=0;a*a<=n/4;a++) for(int b=0;b*b<=n/2;b++) { int k=n-a*a-b*b; int c=h[k]-1; int d=sqrt(k-c*c)+1e-5; if(a*a+b*b+c*c+d*d==n) { cout<<a<<" "<<b<<" "<<c<<" "<<d<<endl; return 0; } } }
分巧克力
题目详情 :
儿童节那天有 K 位小朋友到小明家做客。
小明拿出了珍藏的巧克力招待小朋友们。
小明一共有 N 块巧克力,其中第 i 块是 Hi×Wi 的方格组成的长方形。
为了公平起见,小明需要从这 N 块巧克力中切出 K 块巧克力分给小朋友们。
切出的巧克力需要满足:
- 形状是正方形,边长是整数
- 大小相同
例如一块 6×5 的巧克力可以切出 6 块 2×2 的巧克力或者 2 块 3×3 的巧克力。
当然小朋友们都希望得到的巧克力尽可能大,你能帮小明计算出最大的边长是多少么?
输入格式
第一行包含两个整数 N 和 K。
以下 N 行每行包含两个整数 Hi 和 Wi。
输入保证每位小朋友至少能获得一块 1×1 的巧克力。
输出格式
输出切出的正方形巧克力最大可能的边长。
数据范围
1≤N,K≤10^5,
1≤Hi,Wi≤10^5输入样例:
2 10 6 5 5 6
输出样例:
2
分析:
1、首先,答案肯定在1~100000之间,假定答案为k,那么比k小的都够分但是还不是最大的,比k大就不够分,所以我们可以用二分法
注意:
优先级:乘法>除法
所以在 num+=(siz[i].first/u)*(siz[i].second/u);
这一步中不可以写成num+=(siz[i].first/u*siz[i].second/u);
这样结果就会错误
代码:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=100010;
pair<int,int> siz[N]; //巧克力的尺寸
int n,m; //巧克力块数,和小朋友的个数
bool success(int u)
{
int num=0;
for(int i=0;i<n;i++)
{
//错误!!num+=(siz[i].first/u+siz[i].second/u);
num+=(siz[i].first/u)*(siz[i].second/u);
if(num>=m) return true;//在循环过程中,如果块数大于小朋友个数就返回
}
if(num<m) return false;
else return true;
}
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++)
{
cin>>siz[i].first>>siz[i].second;
}
// sort(siz,siz+n);
// for(int i=0;i<n;i++)
// {
// cout<<siz[i].first<<" "<<siz[i].second<<endl;
// }
int l=1,r=100000;
//找最大的块数=找右边界
while(l<r)
{
int mid=l+(r-l+1>>1);//等价于l+r+1>>1,防止数据溢出
if(success(mid)) l=mid;
else r=mid-1;
}
cout<<l<<endl;
return 0;
}
② 前缀和
前缀和
题目详情:
输入一个长度为 n 的整数序列。
接下来再输入 m 个询问,每个询问输入一对 l,r。
对于每个询问,输出原序列中从第 l 个数到第 r 个数的和。
输入格式
第一行包含两个整数 n 和 m。
第二行包含 n 个整数,表示整数数列。
接下来 m 行,每行包含两个整数 l 和 r,表示一个询问的区间范围。
输出格式
共 m 行,每行输出一个询问的结果。
数据范围
1≤l≤r≤n
1≤n,m≤100000
−1000≤数列中元素的值≤1000
输入样例:
5 3
2 1 3 6 4
1 2
1 3
2 4
输出样例:
3
6
10
分析:
1、此题我们可以采用暴搜,但当数据很大时,暴搜时间复杂度还是比较高的
2、所以我们先把整数序列的前缀和用一个数组f[N}存储下来,r~l区间的值即为f[r]-f[l-1]
数组小标从1开始,防止数组越界
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=100010;
int n,m;
int a[N]; //整数序列
long long f[N]; //前缀和数组了。
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i]; //整数数组
f[i]=f[i-1]+a[i]; //前缀和
}
while(m--)
{
int l,r;
cin>>l>>r;
cout<<f[l]-f[r-1]<<endl;
}
return 0;
}
子矩阵的和
题目详情:
输入一个 n 行 m 列的整数矩阵,再输入 q 个询问,每个询问包含四个整数 x1,y1,x2,y2,表示一个子矩阵的左上角坐标和右下角坐标。
对于每个询问输出子矩阵中所有数的和。
输入格式
第一行包含三个整数 n,m,q。
接下来 n 行,每行包含 m 个整数,表示整数矩阵。
接下来 q 行,每行包含四个整数 x1,y1,x2,y2,表示一组询问。
输出格式
共 q 行,每行输出一个询问的结果。
数据范围
1≤n,m≤1000
1≤q≤200000
1≤x1≤x2≤n
1≤y1≤y2≤m
−1000≤矩阵内元素的值≤1000输入样例:
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<iostream>
#include<algorithm>
using namespace std;
const int N=1001; //不要开太大,内存会超出限制
int a[N][N];
long long f[N][N];
int n,m,q;
int main()
{
cin>>n>>m>>q;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>a[i][j];
//求以(0,0),(0,j),(i,0),(i,j)为顶点的子矩阵的和
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
f[i][j]=a[i][j]+f[i-1][j]+f[i][j-1]-f[i-1][j-1];
while(q--)
{
int x1,y1,x2,y2;
cin>>x1>>y1>>x2>>y2;
cout<<(f[x2][y2]-f[x2][y1-1]-f[x1-1][y2]+f[x1-1][y1-1])<<endl;
}
return 0;
}
激光炸弹
题目详情:
地图上有 N 个目标,用整数 Xi,Yi 表示目标在地图上的位置,每个目标都有一个价值 Wi。
注意:不同目标可能在同一位置。
现在有一种新型的激光炸弹,可以摧毁一个包含 R×R 个位置的正方形内的所有目标。
激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆炸范围,即那个正方形的边必须和 x,y 轴平行。
求一颗炸弹最多能炸掉地图上总价值为多少的目标。
输入格式
第一行输入正整数 N 和 R,分别代表地图上的目标数目和正方形的边长,数据用空格隔开。
接下来 N 行,每行输入一组数据,每组数据包括三个整数 Xi,Yi,Wi,分别代表目标的 x 坐标,y 坐标和价值,数据用空格隔开。
输出格式
输出一个正整数,代表一颗炸弹最多能炸掉地图上目标的总价值数目。
数据范围
0≤R≤10^9
0<N≤10000
0≤Xi,Yi≤5000
0≤Wi≤1000输入样例:
2 1 0 0 1 1 1 1
输出样例:
1
分析:
一个边长R*R的正方形内所有!!!不包含边界!!!在这里要把矩阵里的每一个值想象放在一个小格子里
如图:
由此可见,这也是一个求二维子矩阵的问题,需要比较最大值,先求了再说
注意:开一个数组就可以,开多了小心内存超出限制
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=5010;
int n,m;
int s[N][N];
int main()
{
int cnt,R;
cin>>cnt>>R;
R=min(5001,R);
//n和m可能小于R,会存在边界问题,少枚举很多矩阵
n=m=R;
while(cnt--)
{
int x,y,w;
cin>>x>>y>>w;
x++,y++;
n=max(n,x);
m=max(m,y);
s[x][y]+=w;
}
//预处理前缀和
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
//求炸毁的最大价值
int res;
for(int i=R;i<=n;i++)
for(int j=R;j<=m;j++)
res=max(res,s[i][j]-s[i-R][j]-s[i][j-R]+s[i-R][j-R]);
cout<<res<<endl;
return 0;
}
K倍区间
题目详情:
给定一个长度为 N 的数列,A1,A2,…AN,如果其中一段连续的子序列 Ai,Ai+1,…AjAi, 之和是 K 的倍数,我们就称这个区间 [i,j] 是 K 倍区间。
你能求出数列中总共有多少个 K 倍区间吗?
输入格式
第一行包含两个整数 N 和 K。
以下 N 行每行包含一个整数 Ai。
输出格式
输出一个整数,代表 K 倍区间的数目。
数据范围
1≤N,K≤100000
1≤Ai≤100000输入样例:
5 2 1 2 3 4 5
输出样例:
6
分析:
首先,我们得知道同余关系,即当a和b模K的余数相同时,a-b(a>b)即为K的倍数。
遍历f[n],f[i]前有多少个和它同余的数则加上多少个结果
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1e8;
long long s[N]; //输入的n可能会很大
long long cnt[N]; //记录cnt[s[i]%k]的个数
int n,k; //两个输入
int main()
{
scanf("%d%d",&n,&k);
//计算前n项和
for(int i=1;i<=n;i++)
{
scanf("%lld",&s[i]);
s[i]+=s[i-1];
}
//计算f[i]前有多少个同余数列
long long res=0;
cnt[0]=1;
for(int i=1;i<=n;i++)
{
res+=cnt[s[i]%k];
cnt[s[i]%k]++;
}
cout<<res;
return 0;
}