数的范围
题目大意
给定一个按照升序排列的长度为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
解题思路
裸题二分,二分查找左端点和右端点
整数二分的步骤:
1.找一个区间 [L,R],使得答案一定在该区间中。
2.找一个判断条件,使得该判断条件具有二段性,并且答案一定是该二段性的分界点。
3.分析终点 M 在该判断条件下是否成立,如果成立,考虑答案在哪个区间;如果不成立,考虑答案在哪个区间。
4.如果更新方式写的是 R = Mid,不用做任何处理;如果更新方式是 L = Mid,则需要在计算Mid时 +1。
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 1e5 + 7;
int a[maxn];
int main()
{
int n,q;
scanf("%d %d",&n,&q);
for(int i=0;i<n;++i) scanf("%d",&a[i]);
while(q--)
{
int x;
scanf("%d",&x);
int l=0,r=n-1;
//查找左端点
while(l<r)
{
int mid=l+r>>1;
if(a[mid]>=x) r=mid;
else l=mid+1;
}
if(a[l]!=x) printf("-1 -1\n");
else
{
printf("%d ",l);
r=n-1;
//查找右端点
while(l<r)
{
int mid=l+r+1>>1;
if(a[mid]<=x) l=mid;
else r=mid-1;
}
printf("%d\n",l);
}
}
return 0;
}
数的三次方根
题目大意
给一个浮点数n,求它它的三次方根
输入格式
共一行,包含一个浮点数n。
输出格式
共一行,包含一个浮点数,表示问题的解。
注意,结果保留6位小数。
数据范围
−10000≤n≤10000−10000≤n≤10000
输入样例
1000.00
输出样例
10.000000
解题思路
实数域上二分
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
double l=-10000,r=10000,x;
scanf("%lf",&x);
while(l+1e-8<r)
{
double mid=(l+r)/2;
if(mid*mid*mid>=x) r=mid;
else l=mid;
}
printf("%f",l);
return 0;
}
前缀和
题目大意
输入一个长度为n的整数序列。
接下来再输入m个询问,每个询问输入一对l, r。
对于每个询问,输出原序列中从第l个数到第r个数的和。
输入格式
第一行包含两个整数n和m。
第二行包含n个整数,表示整数数列。
接下来m行,每行包含两个整数l和r,表示一个询问的区间范围。
输出格式
共m行,每行输出一个询问的结果。
数据范围
1≤l≤r≤n1≤l≤r≤n,
1≤n,m≤1000001≤n,m≤100000,
−1000≤数列中元素的值≤1000
输入样例
5 3
2 1 3 6 4
1 2
1 3
2 4
输出样例
3
6
10
解题思路
每读入一个数,你就累计加前一个数,最后求得区间和就是a[r] - a[l-1]的差
#include <iostream>
#include <cstdio>
#include <stdio.h>
using namespace std;
const int maxn=1e5+7;
typedef long long ll;
ll a[maxn];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]),a[i]+=a[i-1];
while(m--)
{
int l,r;
scanf("%d%d",&l,&r);
printf("%lld\n",a[r]-a[l-1]);
}
return 0;
}
子矩阵的和
题目大意
输入一个n行m列的整数矩阵,再输入q个询问,每个询问包含四个整数x1, y1, x2, y2,表示一个子矩阵的左上角坐标和右下角坐标。
对于每个询问输出子矩阵中所有数的和。
输入格式
第一行包含三个整数n,m,q。
接下来n行,每行包含m个整数,表示整数矩阵。
接下来q行,每行包含四个整数x1, y1, x2, y2,表示一组询问。
输出格式
共q行,每行输出一个询问的结果。
数据范围
1≤n,m≤10001≤n,m≤1000,
1≤q≤2000001≤q≤200000,
1≤x1≤x2≤n1≤x1≤x2≤n,
1≤y1≤y2≤m1≤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
解题思路
S[i][j]=S[i-1][j]+S[i][j-1]-S[i-1][j-1]+a[i][j]
边长为R的正方形,得出结论:
∑
x
=
i
−
R
+
1
n
∑
y
=
j
−
R
+
1
n
A
[
x
]
[
y
]
=
S
[
i
,
j
]
−
S
[
i
−
R
,
j
]
−
S
[
i
,
j
−
R
]
+
S
[
i
−
R
,
j
−
R
]
\sum_{x=i-R+1}^n \sum_{y=j-R+1}^n A[x][y]=S[i,j]-S[i-R,j]-S[i,j-R]+S[i-R,j-R]
x=i−R+1∑ny=j−R+1∑nA[x][y]=S[i,j]−S[i−R,j]−S[i,j−R]+S[i−R,j−R]
#include <iostream>
#include <cstdio>
#include <stdio.h>
using namespace std;
const int maxn=1e3+7;
typedef long long ll;
ll a[maxn][maxn],ans[maxn][maxn];
int main()
{
int n,m,q;
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%lld",&a[i][j]);
ans[i][j]=ans[i-1][j]+ans[i][j-1]-ans[i-1][j-1]+a[i][j];
}
}
while(q--)
{
int x,y,tx,ty;
scanf("%d%d%d%d",&x,&y,&tx,&ty);
printf("%lld\n",ans[tx][ty]-ans[x-1][ty]-ans[tx][y-1]+ans[x-1][y-1]);
}
return 0;
}
机器人跳跃问题
题目大意
机器人正在玩一个古老的基于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)≤
1
0
5
10^{5}
105
输入样例
3
1 6 4
输出样例
3
解题思路
二分查找,每次变换都是 2E - H(k),二分查找满足条件的最小能量数值x,满足条件时调整右边边区间的范围,不满足时调整左边的区间范围,直到 l == r 时,输出结果。
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn=1e5+7;
ll a[maxn];
bool judge(ll e,int n)
{
for(int i=1;i<=n;++i)
{
e=(e<<1)-a[i];
if(e>1e5) return true; //剪枝
if(e<0) return false;
}
return true;
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
int l=0,r=1e5;
while(l<r)
{
int mid=l+r>>1;
if(judge(mid,n)) r=mid;
else l=mid+1;
}
printf("%d\n",l);
return 0;
}
四平方和
题目大意
四平方和定理,又称为拉格朗日定理:
每个正整数都可以表示为至多 4 个正整数的平方和。
如果把 0 包括进去,就正好可以表示为 4 个数的平方和。
比如:
5=0
2
^2
2 + 0
2
^2
2 + 1
2
^2
2 + 2
2
^2
2
7=1
2
^2
2 + 1
2
^2
2 + 1
2
^2
2 + 2
2
^2
2
对于一个给定的正整数,可能存在多种平方和的表示法。
要求你对 4 个数排序:0≤a≤b≤c≤d
并对所有的可能表示法按 a,b,c,d
为联合主键升序排列,最后输出第一个表示法。
输入格式
输入一个正整数 N
输出格式
输出4个非负整数,按从小到大排序,中间用空格分开。
数据范围
0<N<5∗10
6
^6
6
输入样例
5
输出样例
0 0 1 2
解题思路
暴力思路O(n
3
^3
3)
for(int i=0;i*i<n;++i)
for(int j=i;i*i+j*j<n;++j)
for(int k=j;i*i+j*j+k*k<n;++k)
{
int t=n-i*i-j*j-k*k;
int d=sqrt(t);
if(d*d==t){
printf("%d %d %d %d\n",i,j,k,d);
return 0;
}
}
二分优化:
先把 c
2
^2
2+d
2
^2
2存起来,然后枚举 a 和 b,二分查找(n-a
∗
*
∗a - b
∗
*
∗b)=node[i].sum的值,当 l = r 时,输出结果
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=5e6+7;
struct Node
{
int sum,c,d;
bool operator < (const Node &t)const
{
if(sum!=t.sum) return sum<t.sum;
if(c!=t.c) return c<t.c;
return d<t.d;
}
}node[maxn];
int main()
{
int idx=0,n;
scanf("%d",&n);
for(int c=0;c*c<=n;++c)
for(int d=c;d*d+c*c<=n;++d)
node[idx++]={c*c+d*d,c,d};
sort(node,node+idx);
for(int a=0;a*a<n;++a)
for(int b=a;b*b+a*a<n;++b)
{
int t=n-a*a-b*b;
int l=0,r=idx-1;
while(l<r)
{
int mid=l+r>>1;
if(node[mid].sum>=t) r=mid;
else l=mid+1;
}
if(node[l].sum==t) {
printf("%d %d %d %d\n",a,b,node[l].c,node[l].d);
return 0;
}
}
return 0;
}
分巧克力
题目大意
儿童节那天有 K 位小朋友到小明家做客。
小明拿出了珍藏的巧克力招待小朋友们。
小明一共有 N 块巧克力,其中第 i 块是 H
i
_i
i×W
i
_i
i的方格组成的长方形。
为了公平起见,小明需要从这 N 块巧克力中切出 K 块巧克力分给小朋友们。
切出的巧克力需要满足:
1.形状是正方形,边长是整数
2.大小相同
例如一块 6×5 的巧克力可以切出 6 块 2×2 的巧克力或者2 块 3×3 的巧克力。
当然小朋友们都希望得到的巧克力尽可能大,你能帮小明计算出最大的边长是多少么?
输入格式
第一行包含两个整数 N 和 K。
以下 N 行每行包含两个整数 H
i
_i
i 和 W
i
_i
i。
输入保证每位小朋友至少能获得一块 1×1的巧克力。
输出格式
输出切出的正方形巧克力最大可能的边长。
数据范围
1≤N,K≤10
5
^5
5
1≤Hi,Wi≤10
5
^5
5
输入样例
2 10
6 5
5 6
输出样例
2
解题思路
二分蛋糕在能够切出 k 块蛋糕的最大尺寸。
#include <cstdio>
using namespace std;
const int maxn=1e5+7;
int a[maxn],b[maxn];
bool judge(int n,int k,int mid)
{
int res=0;
for(int i=0;i<n;++i)
{
res+=(a[i]/mid)*(b[i]/mid);
if(res>=k) return true;
}
return false;
}
int main()
{
int n,k;
scanf("%d%d",&n,&k);
for(int i=0;i<n;i++) scanf("%d %d",&a[i],&b[i]);
int l=0,r=1e5+7;
while(l<r)
{
int mid=l+r+1>>1;
if(judge(n,k,mid)) l=mid;
else r=mid-1;
}
printf("%d\n",l);
return 0;
}
激光炸弹
题目大意
地图上有 N 个目标,用整数 X
i
_i
i,Y
i
_i
i表示目标在地图上的位置,每个目标都有一个价值 W
i
_i
i。
注意:不同目标可能在同一位置。
现在有一种新型的激光炸弹,可以摧毁一个包含 R×R
个位置的正方形内的所有目标。
激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆炸范围,即那个正方形的边必须和 x,y
轴平行。
求一颗炸弹最多能炸掉地图上总价值为多少的目标。
输入格式
第一行输入正整数 N 和 R,分别代表地图上的目标数目和正方形的边长,数据用空格隔开。
接下来 N 行,每行输入一组数据,每组数据包括三个整数 X
i
_i
i, Y
i
_i
i,W
i
_i
i,分别代表目标的 x 坐标,y坐标和价值,数据用空格隔开。
输出格式
输出一个正整数,代表一颗炸弹最多能炸掉地图上目标的总价值数目。
数据范围
0≤R≤10
9
^9
9
0<N≤10000
0≤Xi,Yi≤5000
0≤Wi≤1000
输入样例
2 1
0 0 1
1 1 1
输出样例
1
解题思路
前缀和处理区间,最后求最值。
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=5e3+7;
int ans[maxn][maxn];
int main()
{
int n,r,mx,my;
scanf("%d%d",&n,&r);
mx=my=r;
while(n--)
{
int x,y,w;
scanf("%d%d%d",&x,&y,&w);
++x,++y;
mx=max(mx,x),my=max(my,y);
ans[x][y]+=w;
}
//预处理前缀和
for(int i=1;i<=mx;++i)
for(int j=1;j<=my;++j)
ans[i][j]=ans[i-1][j]+ans[i][j-1]-ans[i-1][j-1]+ans[i][j];
int res=0;
for(int i=r;i<=mx;++i)
for(int j=r;j<=my;++j)
res=max(res,ans[i][j]-ans[i-r][j]-ans[i][j-r]+ans[i-r][j-r]);
printf("%d\n",res);
return 0;
}
k倍区间
题目大意
给定一个长度为 N 的数列,A
1
_1
1,A
2
_2
2,…A
N
_N
N
,如果其中一段连续的子序列 A
i
_i
i,A
i
_i
i+1,…A
j
_j
j之和是 K 的倍数,我们就称这个区间 [i,j]
是 K倍区间。
你能求出数列中总共有多少个 K 倍区间吗?
输入格式
第一行包含两个整数 N 和 K。
以下 N 行每行包含一个整数 A
i
_i
i。
输出格式
输出一个整数,代表 K 倍区间的数目。
数据范围
1≤N,K≤100000,
1≤A
i
_i
i≤100000
输入样例
5 2
1
2
3
4
5
输出样例
6
解题思路
暴力思路,时间复杂度O(n
2
^2
2)
nt res=0;
for(int R=1;R<=n;R++)
for(int L=1;L<=R;L++)
{
int sum=s[R]-s[L-1];
if(!(sum%k)) res++;
}
当 R 固定时,在 0 ~R-1之间能找到多少个满足
(s[R]-s[L-1])%k==0
时间复杂度O(n)代码
#include <cstdio>
using namespace std;
const int maxn=1e5+7;
typedef long long ll;
ll a[maxn],cnt[maxn];
int main()
{
int n,k;
ll res=0;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
a[i]=(a[i-1]+a[i])%k;
res+=cnt[a[i]];
cnt[a[i]]++;
}
printf("%lld\n",res+cnt[0]);
return 0;
}