归并分治_小和问题

归并分治

原理:

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;// 返回结果
	}

}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值