归并分治
原理:
1)思考一个问题在大范围上的答案,是否等于,左部分的答案 + 右部分的答案 + 跨越左右产生的答案
2)计算“跨越左右产生的答案”时,如果加上左、右各自有序这个设定,会不会获得计算的便利性
3)如果以上两点都成立,那么该问题很可能被归并分治解决
4)求解答案的过程中只需要加入归并排序的过程即可,因为要让左、右各自有序,来获得计算的便利性
1.小和问题
测试链接 : https://www.nowcoder.com/practice/edfe05a1d45c4ea89101d936cac32469
描述
数组小和的定义如下:
∑i=1nfi ∑i=1nfi (其中 fi f**i 的定义是第 i 个数的左侧小于等于 si s**i 的个数)
例如,数组 s = [1, 3, 5, 2, 4, 6] ,在 s[0] 的左边小于或等于 s[0] 的数的和为 0 ; 在 s[1] 的左边小于或等于 s[1] 的数的和为 1 ;在 s[2] 的左边小于或等于 s[2] 的数的和为 1+3=4 ;在 s[3] 的左边小于或等于 s[3] 的数的和为 1 ;
在 s[4] 的左边小于或等于 s[4] 的数的和为 1+3+2=6 ;在 s[5] 的左边小于或等于 s[5] 的数的和为 1+3+5+2+4=15 。所以 s 的小和为 0+1+4+1+6+15=27
给定一个数组 s ,实现函数返回 s 的小和
数据范围:0<n≤10^5 0<n≤10^5 , ∣si∣≤100
输入描述:
第一行有一个整数N。表示数组长度
接下来一行N个整数表示数组内的数
输出描述:
一个整数表示答案
示例:
输入:
6
1 3 5 2 4 6
输出:
27
备注:
1⩽N⩽10^5
−100⩽arr[i]⩽100
解答:
小和 = 左半部分的小和 + 右半部分的小和 + 左半部分对右半部分的小和
当左右都分别有序时,sum(左侧对右侧某数的小和) 会一直向右滑动而不会返回,sum累加后面的数即可
ans (右侧所有数在左侧的小和之和) = 每一个sum累加和
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
public class Main {
public static int MAXN = 100001;
public static int[] arr = new int[MAXN];// 进行操作的数组
public static int[] help = new int[MAXN];// 辅助数组
public static int n;// 数组中的有效个数
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
while (in.nextToken() != StreamTokenizer.TT_EOF) {
n = (int) in.nval;
for (int i = 0; i < n; i++) {
in.nextToken();
arr[i] = (int) in.nval;
}
out.println(smallSum(0, n - 1));
}
out.flush();
out.close();
}
// 结果比较大,用int会溢出的,所以返回long类型
// 特别注意溢出这个点,笔试常见坑
// 返回arr[l...r]范围上,小和的累加和,同时把arr[l..r]变有序
// 时间复杂度O(n * logn)
public static long smallSum(int l, int r) {
if (l == r) {// base case
return 0;
}
int m = (l + r) / 2;
return smallSum(l, m) + smallSum(m + 1, r) + merge(l, m, r);
}
// 返回跨左右产生的小和累加和,左侧有序、右侧有序,让左右两侧整体有序
// arr[l...m] arr[m+1...r]
public static long merge(int l, int m, int r) {
// 统计部分
long ans = 0;
for (int j = m + 1, i = l, sum = 0; j <= r; j++) {// 确保每一个右半部分的数都计算到
while (i <= m && arr[i] <= arr[j]) {// 控制左半部分的指针
sum += arr[i++];// sum为j位置的数在左半部分的小和
}
ans += sum;// ans为所有右半部分的数的小和之和
}
// 正常merge 使其有序
int i = l;// help数组要放置的位置 [l ... r ]
int a = l;
int b = m + 1;
while (a <= m && b <= r) {
help[i++] = arr[a] <= arr[b] ? arr[a++] : arr[b++];
}
while (a <= m) {// 如果左半部分还有数
help[i++] = arr[a++];
}
while (b <= r) {// 如果右半部分还有数
help[i++] = arr[b++];
}
for (i = l; i <= r; i++) {// 从辅助数组中复制到原数组
arr[i] = help[i];
}
return ans;// 返回结果
}
}
标签:归并,int,分治,arr,问题,static,数组,new,public
From: https://blog.csdn.net/kiddkid/article/details/140730816