递增三元组 二分 C++ 蓝桥杯

在这里插入图片描述

样例输入

3
1 1 1
2 2 2
3 3 3

样例输出

27

这里如果暴力枚举的话肯定会超时的, 因为n小于10^5,既然是强制要求三个数字完全单调递增,那么可以在固定中间数字的情况下,计算数组a里面有多少个数字比它小,再计算数组c里面有多少个数字比它大即可。

这里需要注意使用二分法可以大大降低时间复杂度,并且写的时候要注意边界问题和极端情况,还要注意在乘法过程中爆int的问题。
有很多细节需要注意,因为题目里提供的例子很不特殊,有一些比较特殊的情况还是得自己造,自己多测试。

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=100000; 
int a[N+10],b[N+10],c[N+10];
bool cmp(int a,int b){
	return a<b;
}
int main()
{
	int n;
	scanf("%d",&n);
    for(int i=0;i<n;i++)	scanf("%d",&a[i]);//大量输入用scanf 
	for(int i=0;i<n;i++)	scanf("%d",&b[i]);
	for(int i=0;i<n;i++)	scanf("%d",&c[i]);
	
	sort(a,a+n,cmp);//二分必须排序 
	sort(b,b+n,cmp);
	sort(c,c+n,cmp);
	
	int num1,num2;
	long long sum=0;//这里要开long long 
	for(int i=0;i<n;i++){
		int l=0,r=n-1; 
		while(l<r){//找到第一个小于等于b[i]的数的下标 
			int mid=(l+r+1) >>1;
			if(a[mid]<b[i]) l=mid;
			else r=mid-1;  
		}
		if(a[l]>=b[i]) num1=0;//这里很重要,有可能l指向的是大于它的数 
		else num1=l+1;
		l=0,r=n-1;
		while(l<r){//找到第一个大于b[i]的数的下标 
			int mid=(l+r) >> 1;
			if(c[mid]>b[i]) r=mid;
			else l=mid+1;
		}
		if(c[r]<=b[i]) num2=0;
		else num2=n-1-r+1;
		//cout<<num1<<" "<<num2<<endl;
		sum+=(long long)num1*num2;//这里容易爆int 
	}
	cout<<sum<<endl;
    return 0;
}
/*
3
1 5 7
2 3 9
3 8 9

3
9 9 9
1 3 5
3 8 9
*/
也可以用一个cpp自带的二分函数;lower_bound()和upper_bound()
函数格式如下,que为长度为n的数组
lower_bound(que,que+n,x)-que //得到的是que数组里大于等于x的第一个数字的下标
upper_bound(que,que+n,x)-que //得到的是que数组里大于x的第一个数字的下标
比起自己写的二分,用自带的也很香。
这两个函数,我测试了一下,对于升序序列,upper_bound和lower_bound函数如果没找到大于x(或大于等于x)的数字,就会指向该数组最后一个值的下一位,如果数组长度为n(有效值0—n-1),那么函数返回的数字是n,基于这一点,我们就可以大胆的使用这个函数的返回值,而不用考虑我们手打二分遇到的得到的指针可能不满足条件还需要特判的这种情况。(2022.3.21更新)
这样我们就可以愉快的使用这两个函数了。

代码如下(2022.3.21更新):

#include<bits/stdc++.h> 
using namespace std;
const int N=100005;
int a[N],b[N],c[N];
bool cmp(int a,int b){
	return a<b;
}
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++)	scanf("%d",&a[i]);
	for(int i=0;i<n;i++)	scanf("%d",&b[i]);
	for(int i=0;i<n;i++)	scanf("%d",&c[i]);
	sort(a,a+n,cmp);
	sort(b,b+n,cmp);
	sort(c,c+n,cmp);
	long long num1,num2;
	long long sum=0;
	for(int i=0;i<n;i++){
		num1=lower_bound(a,a+n,b[i])-a;
		num2=upper_bound(c,c+n,b[i])-c;
		sum+=((num1)*(n-num2));
	}
	cout<<sum<<endl;
    return 0;
}
/*
3
1 5 7
2 3 9
3 8 9

3
9 9 9
1 3 5
3 8 9
*/
方法二:(2022.3.22更新)
这个方法参考y总,使用前缀和的方法。
代码如下:
#include<bits/stdc++.h> //前缀和,有时候会涉及下标为i-1,所以要保证所有的数字都大于1 
using namespace std;
const int N=1e5+5;
int cnt_a[N],cnt_c[N];//存储等于该下标有多少个数字 
int sum_a[N],sum_c[N];//存储小于等于该下标的数字有多少个,也就是前缀和 
int b[N];
int main()
{
	int n,x;
	scanf("%d",&n);
	for(int i=0;i<n;i++){
		scanf("%d",&x);
		cnt_a[x+1]++;
	} 
	for(int i=0;i<n;i++)	scanf("%d",&b[i]),b[i]++;
	for(int i=0;i<n;i++){
		scanf("%d",&x);
		cnt_c[x+1]++;
	}
	for(int i=1;i<N;i++)	sum_a[i]=sum_a[i-1]+cnt_a[i];
	for(int i=1;i<N;i++)	sum_c[i]=sum_c[i-1]+cnt_c[i];
	
	long long ans=0;
	for(int i=0;i<n;i++){
		ans=ans+(long long)sum_a[b[i]-1]*(n-sum_c[b[i]]);
	}//这里会爆int 
	cout<<ans<<endl;
    return 0;
}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值