AcWing 1236. 递增三元组 (flag + 前缀和 | 二分 | 滑动窗口)
程序员文章站
2022-05-03 23:48:15
1236. 递增三元组解题思路最开始想到3重循环枚举三个数组,然后最内层用条件语句判断一下即可,但是数据范围为10510^5105,三重循环肯定会超时那么这道题很可能需要的算法复杂度为O(n)O(n)O(n)或O(nlogn)O(nlogn)O(nlogn),所以只能枚举一个数组,那么枚举哪一个呢?根据题目来看,要求Ai
解题思路
- 最开始想到3重循环枚举三个数组,然后最内层用条件语句判断一下即可,但是数据范围为 1 0 5 10^5 105,三重循环肯定会超时
- 那么这道题很可能需要的算法复杂度为 O ( n ) O(n) O(n)或 O ( n l o g n ) O(nlogn) O(nlogn),所以只能枚举一个数组,那么枚举哪一个呢?
- 根据题目来看,要求 A i < B j < C k A_i<B_j<C_k Ai<Bj<Ck,那么就来枚举B数组
- 此时我们只需要找到A数组中所有小于 B j B_j Bj的数和C数组中所有大于 B j B_j Bj的数,然后将这两个数乘起来就是ans
- 如何找到A数组中所有小于
B
j
B_j
Bj的数和C数组中所有大于
B
j
B_j
Bj的数且在遍历B数组的for循环内部的时间复杂度要低于O(n)
- 方法一:flag + 前缀和
- 方法二:排序 + 二分
- 方法三:滑动窗口
方法一:flag + 前缀和 O(n)
思路:
- 计数排序思想:先初始化flag数组用来记录a数组每个数出现的次数
- 计算flag数组的前缀和数组 s[n],那么 s[i] 就表示从a数组中所有取值在 [0, i] 区间内的元素个数
- 那么计数 a数组 中所有小于 b[j] 的数就相当于求a数组中取值在 [0, b[j] - 1] 区间内的元素个数,即求s[b[j] - 1],b数组同理
import java.util.*;
import java.io.*;
import java.math.*;
class Main {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String sp[];
int N = 100010;
int[] a = new int[N], b = new int[N], c = new int[N];
int n;
int[] flag_a = new int[N];
int[] s_flag_a = new int[N];
int[] flag_c = new int[N];
int[] s_flag_c = new int[N];
void run() throws Exception {
sp = reader.readLine().split(" ");
n = Integer.parseInt(sp[0]);
sp = reader.readLine().split(" ");
for (int i = 0; i < n; i++) {
a[i] = Integer.parseInt(sp[i]);
}
sp = reader.readLine().split(" ");
for (int i = 0; i < n; i++) {
b[i] = Integer.parseInt(sp[i]);
}
sp = reader.readLine().split(" ");
for (int i = 0; i < n; i++) {
c[i] = Integer.parseInt(sp[i]);
}
// 用a数组初始化flag_a数组
for (int i = 0; i < n; i++) {
flag_a[a[i]]++;
flag_c[c[i]]++;
}
s_flag_a[0] = flag_a[0];
s_flag_c[0] = flag_c[0];
// 得到flag_a数组和flag_c数组的前缀和数组
for (int i = 1; i < N; i++) {
s_flag_a[i] = s_flag_a[i - 1] + flag_a[i];
s_flag_c[i] = s_flag_c[i - 1] + flag_c[i];
}
BigInteger cnt = new BigInteger("0");
// 遍历b数组
for (int i = 0; i < n; i++) {
BigInteger cnt_a = new BigInteger(String.valueOf(b[i] - 1 < 0 ? 0 : s_flag_a[b[i] - 1]));
BigInteger cnt_c = new BigInteger(String.valueOf(n - s_flag_c[b[i]]));
cnt = cnt.add(cnt_a.multiply(cnt_c));
}
System.out.println(cnt);
}
public static void main(String[] args) throws Exception {
new Main().run();
}
}
方法二:排序 + 二分 O(nlogn)
注意:
- 二分的条件必须为:a数组和b数组中有一部分大于b[i]和小于b[i]的数,需要特判一下全小于等于或者全大于等于b[i]的情况,否则二分会出错
-
Arrays.sort(int[] arr, int startIndex, int endIndex)
时需要注意的是[startIndex, endIndex),和substring一样 - N的数据范围为 1 0 5 10^5 105,那么最多可能出现的次数为 1 0 10 10^{10} 1010会爆long,所以要用大数类
- 大数类的运算方法不会直接修改大数的值,需要重新赋值
- 调试技巧:当二分出现错误的时候从两个方向考虑,排序和二分
import java.util.*;
import java.io.*;
import java.math.*;
class Main {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String sp[];
int[] a = new int[100010], b = new int[100010], c = new int[100010];
int n;
void run() throws Exception {
sp = reader.readLine().split(" ");
n = Integer.parseInt(sp[0]);
sp = reader.readLine().split(" ");
for (int i = 0; i < n; i++) {
a[i] = Integer.parseInt(sp[i]);
}
sp = reader.readLine().split(" ");
for (int i = 0; i < n; i++) {
b[i] = Integer.parseInt(sp[i]);
}
sp = reader.readLine().split(" ");
for (int i = 0; i < n; i++) {
c[i] = Integer.parseInt(sp[i]);
}
// 对a数组和c数组先排序
Arrays.sort(a, 0, n);
Arrays.sort(c, 0, n);
BigInteger cnt = new BigInteger("0");
// 遍历b数组
for (int i = 0; i < n; i++) {
BigInteger cnt_a = new BigInteger("0");
BigInteger cnt_c = new BigInteger("0");;
// 从a数组中找到所有小于b[i]的数
// 其最大值比b[i]还小那么cnt_a = n
// 其最小值不小于b[i]那么cnt_a = 0
// 有一部分小于b[i],一部分大于b[i],则用二分找到第一个大于b[i]的数的下标
if (a[n - 1] < b[i]) cnt_a = new BigInteger(String.valueOf(n));
else if (a[0] >= b[i]) cnt_a = new BigInteger(String.valueOf(0));
else cnt_a = new BigInteger(String.valueOf(searchFirstBigOrEqualNum(b[i])));
// 从c数组中找到所有大于b[i]的数
// 其最小值比b[i]还那么cnt_c = n
// 其最大值不大于b[i]那么cnt_c = 0
if (c[0] > b[i]) cnt_c = new BigInteger(String.valueOf(n));
else if (c[n - 1] <= b[i]) cnt_c = new BigInteger(String.valueOf(0));
else cnt_c = new BigInteger(String.valueOf(n - searchFirstBigNum(b[i])));
cnt = cnt.add(cnt_a.multiply(cnt_c));
}
System.out.println(cnt);
}
int searchFirstBigOrEqualNum(int target) {
int l = 0, r = n - 1;
while (l < r) {
int mid = (l + r) / 2;
if (a[mid] < target) {
l = mid + 1;
} else {
r = mid;
}
}
return l;
}
int searchFirstBigNum(int target) {
int l = 0, r = n - 1;
while (l < r) {
int mid = (l + r) / 2;
if (c[mid] <= target) {
l = mid + 1;
} else {
r = mid;
}
}
return l;
}
public static void main(String[] args) throws Exception {
new Main().run();
}
}
本文地址:https://blog.csdn.net/k909397116/article/details/109189695
上一篇: 简要介绍一下JMS规范