蓝桥杯之二分与前缀和

二分板子?第一次和最后一次出现的位置

目的总是取到一个最合适的值。
首先,找到取值的范围,在该范围内进行二分。
判断取值是否满足题意条件
……

整数二分模板一共有两个,分别适用于不同情况。
算法思路:假设目标值在闭区间 [ l , r ] [l, r] [l,r]中, 每次将区间长度缩小一半,当 l = r l = r l=r时,我们就找到了目标值。

模板一:(再也不用为边界的处理而头痛了)
当我们将区间 [ l , r ] [l, r] [l,r]划分成 [ l , m i d ] [l, mid] [l,mid] [ m i d + 1 , r ] [mid + 1, r] [mid+1,r]时,其更新操作是 r = m i d r = mid r=mid或者 l = m i d + 1 l = mid + 1 l=mid+1;,计算 m i d mid mid时不需要加1。

int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    return l;
}

模板二:
当我们将区间 [ l , r ] [l, r] [l,r]划分成 [ l , m i d − 1 ] [l, mid - 1] [l,mid1] [ m i d , r ] [mid, r] [mid,r]时,其更新操作是 r = m i d − 1 r = mid - 1 r=mid1或者 l = m i d l = mid l=mid;,此时为了防止死循环,计算 m i d mid mid时需要加1。

边界的处理:
死循环的解释是:例如二分区间缩小到 l=3,r=4 时,l=mid ,mid的算法一定是向上取整,否则一直取的是3
或者解释为,要保持一致的区间分割方式

int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

这两种情况就是一个是从小到大找,一个是从大到小找。因为二分的前提是要有序(一般都是,搜索的范围中值是升序的,寻找满足条件的最小值,那么当adequate(mid),对应的就是 模板一 r=mid,
相反,如果要找满足条件的最大值,那么当adequate(mid),对应的就是 模板二 l=mid

浮点数二分

bool check(double x) {/* ... */} // 检查x是否满足某种性质

double bsearch_3(double l, double r)
{
    const double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;//不能是mid减1噢,这可是浮点数,1是精度的巨大倍 
    }
    return l;
}

在这里插入图片描述

#include <iostream>
#include <stdlib.h>
#include <algorithm>
using namespace std;
int n,q; 
const int N=1e5+5;
int a[N];
void search(int t){
	int l=0;int r=n-1;
	while(l<r){
		int mid=l+(r-l)/2;
		if(a[mid]<t){
			l=mid+1;
		}
		else r=mid;
	};
	if(a[l]!=t){
		cout<<"-1 -1";
		return ;
	}
	cout<<l<<" ";
	r=n-1;
	while(l<r){
		int mid=l+(r-l+1)/2;
		if(a[mid]<=t){
			l=mid;
		}
		else r=mid-1;
	}
	cout<<l;
}

int main(){
	cin>>n>>q;
	for(int i=0;i<n;i++){
		cin>>a[i];
	}
	sort(a,a+n);//题外话:第3个参数 greater<int>()
	int t;
	while(q--){
		cin>>t;
		search(t);
		if(q)cout<<endl;
	}
	return 0;
} 

机器人跳跃问题

根据题意可知,每过一个建筑,能量 E E E 会转变成 2 E − H [ i ] 2E-H[i] 2EH[i]
随初始能量 E E E 单调增长,可以对初始能量 E E E 进行二分

	if(x>=1e5)return true;

判断二分值是否合法时,要防止爆int变成负值,那么将永远不合法,永远返回二分的最右边的值
n最大可达1e5,按照x的计算方式(指数爆增),
2^(1e5)早就爆了longlong变成一个负值 ,于是返回二分的最右边的值

#include <iostream>
#include <stdlib.h>
#include <string.h>

using namespace std;
int n; 
const int N=1e5+5;
int a[N];
bool adequate(int x){
	for(int i=0;i<n;i++){
		x=x*2-a[i];
		if(x<0)return false;
		if(x>=1e5)return true;//n最大可达1e5,按照x的计算方式,
//		2^(1e5)早就爆了longlong变成一个负值 ,于是返回二分的最右边的值 
	}
	return true;
}
int main(){
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>a[i];
	}
	int l=0,r=1e5;
	while(l<r){
		int mid=l+(r-l)/2;
		if(adequate(mid))r=mid;
		else l=mid+1;
	}
	cout<<l;
	return 0;
} 

四平方和

在这里插入图片描述
思路速递
数量级 1 e 6 1e6 1e6,复杂度应该为 n n n 或者 n l o g n nlogn nlogn
1、abcd皆小于 n \sqrt{n} n ,暴力枚举abc,时间复杂度为 n ∗ n ∗ n = n n \sqrt{n}*\sqrt{n}*\sqrt{n}=n\sqrt{n} n n n =nn ,不可行
2、先枚举cd所有可能的组合的和,存放在哈希表中,接下来枚举ab,在查找 n − ( a + b ) n-(a+b) n(a+b) 是否在哈希表中存在

存放 这一步非常有门道,首先 “输出第一个表示法”,只需保存字典序最小的 c d cd cd 组合, 故想用map容器存放,只需要map 而不需要 unordered_map
或者 直接将 c d cd cd 组合存放在数组中,直接 O ( 1 ) O(1) O(1) 查询,时间复杂度为 O ( n ) O(n) O(n)
或者将所有 c d cd cd 组合(和相同的也全部存下)存放在 vector 数组中,则需要根据 cd和排序,内部按字典序排序(cd和相同按cd字典序),再二分,时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn) 如此

存放在map容器中,超时

#include <iostream>
#include <stdlib.h>
#include <algorithm>
using namespace std;
int n; 
const int N=1e5+5;
int a[N];
#define PII pair<int,int> 
#include <map>
map<int,PII> mp;

int main(){
	cin>>n;
	for(int c=0;c*c<n;c++){
		for(int d=c;d*d<=n;d++){
			int t=c*c+d*d;
			if(mp.find(t)==mp.end()){
				mp.insert({t,{c,d}});
			}
		}
	}
		for(int a=0;a*a*4<=n;a++){
		for(int b=a;a*a+b*b<=n/2;b++){
			int t=n-a*a-b*b;
			if(mp.find(t)!=mp.end()){
				cout<<a<<" "<<b<<" "<<mp[t].first<<" "<<mp[t].second<<endl;
				return 0;
			}
		}
	}
	return 0;
} 

像只枚举ab,借助 n − a ∗ a − b ∗ b n-a*a-b*b naabb 搜索剩余两个一样
存放时,可以存放部分信息,剩下的部分通过条件计算得到

#include <iostream>
#include <stdlib.h>
#include <math.h>
#include <algorithm>
using namespace std;
int n; 
const int N=1e5+5;
int a[N];
#define PII pair<int,int> 
#include <map>
map<int,int> mp;

int main(){
	cin>>n;
	for(int c=0;c*c<n;c++){
		for(int d=c;d*d<=n;d++){
			int t=c*c+d*d;
			if(mp.find(t)==mp.end()){
				mp.insert({t,c});
			}
		}
	}
		for(int a=0;a*a*4<=n;a++){
		for(int b=a;a*a+b*b<=n/2;b++){
			int t=n-a*a-b*b;
			if(mp.find(t)!=mp.end()){
				cout<<a<<" "<<b<<" "<<mp[t]<<" "<<sqrt(t-mp[t]*mp[t])<<endl;
				return 0;
			}
		}
	}
	return 0;
} 

进而不需要利用map容器,直接存放在数组中(cd 组合的值、c值)

#include <iostream>
#include <stdlib.h>
#include <math.h>
#include <algorithm>
using namespace std;
int n; 
const int N=5e6+5;
int p[N];
#define PII pair<int,int> 
#include <map>
map<int,int> mp;

int main(){
	cin>>n;
	for(int c=0;c*c<n;c++){
		for(int d=c;d*d<=n;d++){
			int t=c*c+d*d;
			if(t>n)continue;//否则t值很大,数组越界 
			if(!p[t]){
				p[t]=c+1;//存放的值与0错开,0默认为之前未出现过,真正的0存的是1 
			}
		}
	}
		for(int a=0;a*a*4<=n;a++){
		for(int b=a;a*a+b*b<=n/2;b++){
			int t=n-a*a-b*b;
			if(p[t]){
				int c=p[t]-1;
				cout<<a<<" "<<b<<" "<<c<<" "<<sqrt(t-c*c)<<endl;
				return 0;
			}
		}
	}
	return 0;
} 

分巧克力?典型二分找大的(从右往左找)

#include <iostream>
#include <stdlib.h>
#include <math.h>
#include <algorithm>
using namespace std;
int n,k; 
const int N=1e5+5;
int h[N]; 
int w[N];
bool adequate(int x){
	int cnt=0;
	for(int i=0;i<n;i++){
		if(h[i]<x||w[i]<x)continue;
		cnt+=(h[i]/x)*(w[i]/x);
	}
	if(cnt>=k)return true;
	else return false;
}
int search(int l,int r){
	while(l<r){
		int mid=l+(r-l+1)/2;
		if(adequate(mid)){
			l=mid;
		}
		else r=mid-1;
	}
	return l;
}
int main(){//19
	cin>>n>>k;
	int l=1;
	int r=0;
	for(int i=0;i<n;i++){
		cin>>h[i]>>w[i];
		r=max(r,h[i]);
		r=max(r,w[i]);
	}
	cout<<search(l,r);
	return 0;
} 

二分upper_bound(a+1,a+n+1,x)-a?递增三元组

xy也要取longlong,否则xy相乘就会强制转换为int,导致错误

#include <iostream>
#include <stdlib.h>
#include <algorithm>
using namespace std;
#define ll long long int
int n; 
const int N=1e5+5;
int a[N];
int b[N];
int c[N];
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=n;i++){
		cin>>b[i];
	}
	for(int i=1;i<=n;i++){
		cin>>c[i];
	}
	sort(a + 1, a + 1 + n);
    sort(b + 1, b + 1 + n);
    sort(c + 1, c + 1 + n);
    ll res=0;
	for(int i=1;i<=n;i++){
		ll x=(lower_bound(a+1,a+n+1,b[i])-a)-1;
		ll y=n-(upper_bound(c+1,c+n+1,b[i])-c)+1;
		res+=x*y;
	}
	cout<<res;
	return 0;
} 

前缀和取余?K倍区间

区间和等于k的倍数无非两中情况
一:区间 1 ~ i,这种只需要计算 求余前缀和为0的次数
二:区间 i ~ j,这种处于里段的区间段,在 两个相等的前缀和 之间
对前缀和取模之后,两个相等的前缀和就能组成一个k倍区间
注意,res 是区间个数,n的平方级别,会爆int

#include <iostream>
#include <stdlib.h>
#include <algorithm>
using namespace std;
#define ll long long int
int n,k; 
const int N=1e5+5;
int s[N];
int cnt[N];
signed main(){
	cin>>n>>k;
	ll res=0;
	int x;
	for(int i=1;i<=n;i++){
		cin>>x;
		s[i]=(s[i-1]+x)%k;
		res+=cnt[s[i]];//两个相同的前缀和 
		cnt[s[i]]++;
	}
	cout<<res+cnt[0];
	return 0;
} 

上来就一个TLE,真难过

#include <iostream>
#include <stdlib.h>
#include <algorithm>
using namespace std;
//#define ll long long int
int n,k; 
const int N=1e5+5;
int a[N];
int s[N];
signed main(){
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		s[i]=s[i-1]+a[i];
	}
	int cnt=0;
	for(int i=1;i<=n;i++){
		for(int j=i;j<=n;j++){
			if((s[j]-a[i-1])%k==0)cnt++;
		}
	}
	cout<<cnt;
	return 0;
} 

二维前缀和?激光炸弹


1、不能开两个数组(直接在原数组上求二维前缀和就好),因为每个数组是5000x5000这么大,就是5000x5000x4bytes,5000x5000x4/1024/1024=95MB,这题的空间是168MB,所以两个数组会MLE。
一个数组就行了,在自己身上求前缀和。

2、 xy坐标是从0开始的。

边界问题:

	r=min(r,max(mx,my));

r是不能减小的,比如 4 5 3,
不能因为最后一个双重循环从r开始,r必须小于mx,my,就随意减小r
且 为了得到最佳res,极有可能出现r的范围 圈住了空地(即超出了5*3范围)
故而,只能选择将mx,my搞大,一定要都大于r,才能让r肆意圈地
这样一来,4 5 4
求前缀和时不能只求 行1~ 5,列1~ 3 的,否则 例如列1~4的前缀和就会误以为0,实际上等于3列所有

	r=min(r,5001);  ✔
	mx=max(mx,r);
	my=max(my,r); 

但 r 超过a数组的大小也没有意义

#include <iostream>
#include <stdlib.h>
#include <math.h>
#include <algorithm>
using namespace std;
//#define ll long long int
int n,r; 
const int N=5005;
int a[N][N];
//int sum[N][N];
signed main(){//30
	cin>>n>>r;
	int x,y,w;
	int mx=0,my=0;
	for(int i=1;i<=n;i++){
		cin>>x>>y>>w;
		a[x+1][y+1]+=w;
		mx=max(mx,x+1);
		my=max(my,y+1);
	}
	r=min(r,5001);
	mx=max(mx,r);
	my=max(my,r);
	int res=0;
	for(int i=1;i<=mx;i++){//二维矩阵前缀和 
		for(int j=1;j<=my;j++){
			a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];
		}
	}
	for(int i=r;i<=mx;i++){//二维矩阵前缀和 
		for(int j=r;j<=my;j++){
			int tmp=a[i][j]-a[i-r][j]-a[i][j-r]+a[i-r][j-r];
			res=max(res,tmp); 
		}
	} 
	cout<<res;
	return 0;
} 
//边界问题,求边长为r正方形内总和,借助矩阵前缀和 
//	1 2 3 4 5
//	1 2 3 4 5
//	1 2 3 4 5
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值