★二分与前缀和★

目录

①二分

数的范围

数的三次方根

机器人跳跃问题

四平方和

分巧克力 

② 前缀和

前缀和

子矩阵的和

激光炸弹

K倍区间


①二分

一般如果题目的答案在一个有序区间,且具有两段性,即在它前面的数据满足一个性质,而在它后面的性质也满足一个性质,即可用二分法

数的范围

 题目详情:

给定一个按照升序排列的长度为 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 块巧克力分给小朋友们。

切出的巧克力需要满足:

  1. 形状是正方形,边长是整数
  2. 大小相同

例如一块 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;
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值