小单刷题笔记之递增三元组(前缀和/二分)

给定三个整数数组

A=[A1,A2,…AN]
B=[B1,B2,…BN]
C=[C1,C2,…CN]

请你统计有多少个三元组 (i,j,k)(i,j,k) 满足:

  1. 1≤i,j,k≤N
  2. Ai<Bj<Ck

输入格式

第一行包含一个整数 N。

第二行包含 N 个整数 A1,A2,…AN

第三行包含 N 个整数 B1,B2,…BN

第四行包含 N 个整数 C1,C2,…CN

输出格式

一个整数表示答案。

数据范围

1≤N≤1e5
0≤Ai,Bi,Ci≤1e5

输入样例:

3
1 1 1
2 2 2
3 3 3

输出样例:

27

思路:

1)暴力:枚举所有数,时间复杂度O(n^3),此题N的规模为1e5,大概是1e15,此法TLE,实在没思路打蓝桥可以骗分;

#include<iostream>
#include<algorithm>
using namespace std;
const int N=100010;
int a[N],b[N],c[N];


int main(){
	int n;cin>>n;
	int ans=0;
	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]);
	
	for(int i=0;i<n;++i){
		for(int j=0;j<n;++j){
			for(int k=0;k<n;k++){
				if(a[i]<b[i]&&b[i]<c[i]){
					ans++;
				}
			}
		}
	}
	cout<<ans;
	
	return 0;
} 

2)排序+二分,复杂度为O(n*logn),大概1e7的一个复杂度,是可以AC的,我这里直接用了c++的upper_bound和lower_bound来求。就是只枚举b数组,然后找到a数组比b[i]小的所有数的个数*c数组所有比b[i]大的个数。要注意下,这俩函数返回的是指针,计算的时候要减去一个

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>

using namespace std;
const int N=1e5+5;
int a[N],b[N],c[N];
int n;
int main(){
    cin>>n;


    for(int i=0;i<n;++i)cin>>a[i];
    for(int i=0;i<n;++i)cin>>b[i];
    for(int i=0;i<n;++i)cin>>c[i];
    sort(a,a+n);
    sort(c,c+n);
    
    int ans=0;
    for(int i=0;i<n;++i){
        int *pc=upper_bound(c,c+n,b[i]);//第一个大于b[i]的下标
        int *pa=lower_bound(a,a+n,b[i]);//第一个等于b[i]的下标
    
	    int *cc=c;
        int *aa=a;
	
		int numc=pc-cc;
	    int numa=pa-aa;
    
	    numc=n-numc;
       
        ans+=numc*numa;
    }
    cout<<ans;
    
    return 0;
}

3)前缀和+桶排序思想 时间复杂度O(n),

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
const int N=1e5+5;
int a[N],b[N],c[N];
int cnt[N],s[N];
int as[N],cs[N];
int main(){
    int n;cin>>n;
    for(int i=0;i<n;++i)scanf("%d",&a[i]),a[i]++;
    for(int i=0;i<n;++i)scanf("%d",&b[i]),b[i]++;
    for(int i=0;i<n;++i)scanf("%d",&c[i]),c[i]++;
    
    for(int i=0;i<n;++i)cnt[a[i]]++;
    for(int i=1;i<N;++i)s[i]=s[i-1]+cnt[i];
    for(int i=0;i<n;++i)as[i]=s[b[i]-1];
    
    memset(cnt,0,sizeof cnt);
    memset(s,0,sizeof s);
    
    for(int i=0;i<n;++i)cnt[c[i]]++;
    for(int i=1;i<N;++i)s[i]=s[i-1]+cnt[i];
    for(int i=0;i<n;++i)cs[i]=s[N-1]-s[b[i]];
    
    LL ans=0;
    for(int i=0;i<n;i++)ans+=(LL)as[i]*cs[i];
    cout<<ans;
    
    
    
    
    
    return 0;
}

用到前缀和一般都需要把区间+1,因为转移的时候需要用到前一个s[i-1]。这个思路也是枚举b的位置,因为枚举b的位置以后,a中小于b[i]的个数和c中大于b[i]的个数是固定的。先统计一下a中所有数出现的次数,存在cnt中,再求cnt的前缀和s,意思就是:前i个数出现的总次数。(都不用排序了hh),最后再求一下as,等于s中所有小于b[i]的数出现的个数和。c基本上同理。

今天你刷算法题了吗? 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值