算法1 (前缀和)
-
1、
acnt[i]
表示在A
中i
这个值出现多少次,ccnt[i]
表示在C
中i
这个值出现多少次 -
2、关键的一步,通过
s[]
记录当前数组从1
到i
中,所有数出现的次数的前缀和 -
3、
as[]
记录在A
中由多少个数小于B[i]
,as[i] = s[b[i] - 1]
-
4、
cs[]
记录在C
中由多少个数大于B[i]
,cs[i] = s[N - 1] - s[b[i]]
-
5、由于
a[]
和c[]
互斥,通过乘法原理可得res += as[i] * cs[i]
时间复杂度 $O(n)$
参考文献
算法提高课
Java 代码
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
public class Main {
static int N = 100010;
static int[] a = new int[N];
static int[] b = new int[N];
static int[] c = new int[N];
static int[] acnt = new int[N];//acnt[i]表示在A中i这个值出现多少次
static int[] ccnt = new int[N];//ccnt[i]表示在C中i这个值出现多少次
static int[] as = new int[N];//记录在A中由多少个数小于B[i]
static int[] cs = new int[N];//记录在C中由多少个数大于B[i]
static int[] s = new int[N];//求从1到i中,所有数出现的次数的前缀和
public static void main(String[] args) throws NumberFormatException, IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(reader.readLine().trim());
String[] s1 = reader.readLine().split(" ");
String[] s2 = reader.readLine().split(" ");
String[] s3 = reader.readLine().split(" ");
for(int i = 1;i <= n;i++) a[i] = Integer.parseInt(s1[i - 1]) + 1;
for(int i = 1;i <= n;i++) b[i] = Integer.parseInt(s2[i - 1]) + 1;
for(int i = 1;i <= n;i++) c[i] = Integer.parseInt(s3[i - 1]) + 1;
//求as[]
for(int i = 1;i <= n;i++) acnt[a[i]] ++;
for(int i = 1;i <= N - 1;i++) s[i] = s[i - 1] + acnt[i];
for(int i = 1;i <= n;i++) as[i] = s[b[i] - 1];
//求cs[]
for(int i = 1;i <= n;i++) ccnt[c[i]] ++;
for(int i = 1;i <= N - 1;i++) s[i] = s[i - 1] + ccnt[i];
for(int i = 1;i <= n;i++) cs[i] = s[N - 1] - s[b[i]];
//枚举每个b[i]
long res = 0;
for(int i = 1;i <= n;i++) res += (long)as[i] * cs[i];
System.out.println(res);
}
}
算法2(二分法)
-
1、对
3
个数组分别进行从小到大排序 -
2、找到
a[]
数组中最小的大于等于bi
的元素位置la
,便可知小于bi
的个数为num1
-
3、找到
c[]
数组中最大的小于等于bi
的元素位置lb
,便可知大于bi
的个数为num2
-
4、由于
a[]
和c[]
互斥,通过乘法原理可知符合条件的个数为num1 * num2
注意:当la == 0
或者lb == n + 1
时,则表示不符合条件
时间复杂度 $O(nlogn)$
参考文献
算法提高课
Java 代码
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
public class Main {
static int N = 100010;
static int[] a = new int[N];
static int[] b = new int[N];
static int[] c = new int[N];
public static void main(String[] args) throws NumberFormatException, IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(reader.readLine().trim());
String[] s1 = reader.readLine().split(" ");
String[] s2 = reader.readLine().split(" ");
String[] s3 = reader.readLine().split(" ");
for(int i = 1;i <= n;i++) a[i] = Integer.parseInt(s1[i - 1]);
for(int i = 1;i <= n;i++) b[i] = Integer.parseInt(s2[i - 1]);
for(int i = 1;i <= n;i++) c[i] = Integer.parseInt(s3[i - 1]);
Arrays.sort(a,1,n + 1);
Arrays.sort(b,1,n + 1);
Arrays.sort(c,1,n + 1);
long res = 0;
//枚举b[]数组
for(int i = 1;i <= n;i++)
{
//找到a[]数组中最小的大于等于bi的元素位置
int la = 0,ra = n + 1;
while(la < ra)
{
int mid = (la + ra) >> 1;
if(a[mid] >= b[i]) ra = mid;
else la = mid + 1;
}
//找到c[]数组中最大的小于等于bi的元素位置
int lb = 0,rb = n + 1;
while(lb < rb)
{
int mid = (lb + rb + 1) >> 1;
if(c[mid] <= b[i]) lb = mid;
else rb = mid - 1;
}
if(la == 0 || lb == n + 1) continue;
res += ((long)(la - 1) *(n - lb));
}
System.out.println(res);
}
}
大佬,想问一下,啥时候用个split() 啥时候用个trim(),还有就是啥时候readLine()啥时候nextLine()
for(int i = 1;i <= n;i++) cs[i] = s[N - 1] - s[b[i]];
为什么在求比b[i]大的数时是s[N-1],不是s[N].
看你s[]是定义成s[N]还是s[N+1]了,如果是后者你这么写就没问题了
for(int i = 1;i <= N - 1;i++) s[i] = s[i - 1] + acnt[i];请问i为什么必须要小于N啊?等于N就错了。。
前缀和写法里的
越界
当你求 a 中小于 b 的个数时,是 as[i] = s[b[i] - 1] 对吧,但是数据范围中 值是可以取0的,这个时候的s[b[i]-1]中的b[i]可能会是0,那么0-1就越界了,这也是为什么赋值的时候需要给abc三个数组中的每个数都+1,既然加了1,那么一开始的数据范围就由 0 <= Ai,Bi,Ci <= N 变成了 1 <= Ai,Bi,Ci <= N + 1 ,这个时候你的ABC三个数组中就不存在值为0的数据了。
前缀和数组从1开始是为了 s[i - 1] 不越界,然后就是(这里的N是100010) <= N - 1 和 <= N 的问题,很明显 你数组开了N是 0~N-1,你怎么能 <= N 呢,换句话来说 我们这里 最大值也是 N + 1(数据范围的N是100000),但是我们的N一开始就是多开了 10个空间,这里N+1也不会影响到最终的结果。
怎么确保 i<j<k 呢
按照枚举顺序确保
题目说了要保证 i < j < k了吗?
题目是1≤i,j,k≤N,没要求三个数的大小关系
我想请教一下,为什么二分查找的两个mid的求法不一样呢?
一个是找最小的大于等于bi的元素位置,一个是找最大的小于等于bi的元素位置,具体二分模型去搜y总的二分模版
好的,十分感谢!
我想问一下,这里二分的左右为什么是[0,n+1],不能是[1,n]
若不能找到则一定会在0或者n + 1的位置,否则就表示能找到,这样子可以区分是否能找到这样的对
谢谢,搞明白了
我想请教一下什么时候用Buffer什么时候用Scanner呢?
如果输入的数据的数量达到1万以上的话用Buffer比较好,输入数据的数量比较小的话两个差不多
我看有人用private static BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));这个 那和System.out.println 有什么区别呢?
通俗的来讲,如果输出很庞大的时候,用BufferWriter,这样会快很多,一般的输出用System.out.println就可以了,
https://www.acwing.com/problem/content/175/
你可以做一下这个题目,这个题目的输出就得用BufferedWriter,具体在哪个范围才用我也还没摸清楚hh,毕竟一般用System.out.println都能解决嗯嗯,好滴,谢谢啦
我推荐你封装一个 scanner类。 里面有
next_int
,next_string
等等的helper 函数。 以后做题方便点。输入输出的感觉还行,有时输入Scanner和Buffer还得看情况用,谢谢您的推荐。