树状数组、线段树 | P8613 [蓝桥杯 2014 省 B] 小朋友排队C++题解

P8613 [蓝桥杯 2014 省 B] 小朋友排队

原题链接

题目描述

n n n 个小朋友站成一排。现在要把他们按身高从低到高的顺序排列,但是每次只能交换位置相邻的两个小朋友。

每个小朋友都有一个不高兴的程度。开始的时候,所有小朋友的不高兴程度都是 0 0 0

如果某个小朋友第一次被要求交换,则他的不高兴程度增加 1 1 1,如果第二次要求他交换,则他的不高兴程度增加 2 2 2(即不高兴程度为 3 3 3),依次类推。当要求某个小朋友第 k k k 次交换时,他的不高兴程度增加 k k k

请问,要让所有小朋友按从低到高排队,他们的不高兴程度之和最小是多少。

如果有两个小朋友身高一样,则他们谁站在谁前面是没有关系的。

输入格式

输入的第一行包含一个整数 n n n,表示小朋友的个数。

第二行包含 n n n 个整数 H 1 , H 2 ⋯ H n H_1,H_2 \cdots H_n H1,H2Hn,分别表示每个小朋友的身高。

输出格式

输出一行,包含一个整数,表示小朋友的不高兴程度和的最小值。

输入输出样例 #1

输入 #1

3
3 2 1

输出 #1

9

说明/提示

【样例说明】

首先交换身高为 3 3 3 2 2 2 的小朋友,再交换身高为 3 3 3 1 1 1 的小朋友,再交换身高为 2 2 2 1 1 1 的小朋友,每个小朋友的不高兴程度都是 3 3 3,总和为 9 9 9

【数据规模与约定】

对于 10 % 10\% 10% 的数据, 1 ≤ n ≤ 10 1 \le n \le 10 1n10

对于 30 % 30\% 30% 的数据, 1 ≤ n ≤ 1000 1 \le n \le 1000 1n1000

对于 50 % 50\% 50% 的数据, 1 ≤ n ≤ 10000 1 \le n \le 10000 1n10000

对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 100000 1 \le n \le 100000 1n100000 0 ≤ H i ≤ 1000000 0 \le H_i \le 1000000 0Hi1000000

时限 1 秒, 256M。蓝桥杯 2014 年第五届省赛

// 解题思路,具体见代码:
// 每个小朋友要交换的次数为:前面比他大的数的个数 + 后面比他小的数的个数(可以想象一下冒泡排序)
// 考虑时间复杂度,我们选用树状数组O(logn)来查询区间和
// 最后求出每个小朋友的交换次数之后,由于小朋友的不高兴度是递增的,所以递增数列求和公式求不高兴度

树状数组解法

#include 
#define endl '\n'
#define int long long
using namespace std;
const int N = 1000005;
int n, h[N], ans[N];
int tr[N];
// 每个小朋友要交换的次数为:前面比他大的数的个数 + 后面比他小的数的个数(可以想象一下冒泡排序)
// 考虑时间复杂度,我们选用树状数组O(logn)来查询区间和
// 最后求出每个小朋友的交换次数之后,由于小朋友的不高兴度是递增的,所以递增数列求和公式求不高兴度
int lowbit(int x) {
	return x & -x;
}
void add(int u, int v) {
	for (int i = u; i < N; i+=lowbit(i))  tr[i] += v;  // 注意是i < N
}
int query(int u) {
	int res = 0;
	for (int i = u; i; i -= lowbit(i)) res += tr[i];
	return res;
}
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n;
	for (int i = 1; i <= n; i++)cin >> h[i], h[i]++;  // 注意树状数组的下标从  1  开始
	// 先求前面大的数
	for (int i = 1; i <= n; i++) {
		// 本题是建立树状数组是每个数h[i]是否出现
		// query(N - 1) - query(h[i])是查询数值大小为(h[i],N)中有已经出现数的个数
		// 所以我们要边查询边建立树状数组,这样才可以查询当前数前面的数在(h[i],N)范围出现的个数
		ans[i] += query(N - 1) - query(h[i]);  
		add(h[i], 1);  // add在查询前后进行都可以,因为当前查询设计不到
	}
	
	memset(tr, 0, sizeof tr);  // 重新初始化树状数组,求后面小的数
	for (int i = n; i >= 1; i--) {
		// 和前面一样,我们要边查询边建立树状数组,这样才可以查询当前数后面的数在( 1,h[i]-1 ]范围出现的个数
		ans[i] += query(h[i] - 1);
		add(h[i], 1);
	}
	int res = 0;
	for (int i = 1; i <= n; i++)res += ans[i] * (ans[i] + 1) / 2;
	cout << res;
	return 0;
}

线段树解法

#include 
#define endl '\n'
#define ll long long
using namespace std;
const int N = 1000005;
int n, h[N], ans[N];
struct  node{
	int l, r;
	int sum;
}tr[N*4];
void pushup(int u) { //根据子节点计算第u个节点
	tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void build(int u, int l, int r) { //从根节点开始,1~n的范围构建树
	if (l == r) tr[u] = { l,r };
	else {
		tr[u] = { l,r };
		int mid = (l + r) >> 1;
		build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
		pushup(u);
	}
}
int query(int u, int l, int r) {//从根节点开始查询l~r的区间值
	//注意 l~r是要查询的范围,tr[u].l~tr[u].r是这个节点的值范围
	if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum; //如果要查询的范围包括tr[u].l~tr[u].r
	//直接返回,不要分割
	int mid = (tr[u].l + tr[u].r) >> 1;
	int sum = 0;
	if (mid >= l) sum += query(u << 1, l, r); //*** 如果当前断点坐标有要求的范围就递归
	if (mid < r) sum += query(u << 1 | 1, l, r);// **注意:没有等于号,因为右半边为mid+1
	return sum;
}
void modify(int u, int x, int v) {// 从根节点开始,对第x个数,加上v
	if (tr[u].l == tr[u].r) tr[u].sum += v;  //  // 递归的结束条件,tr[u]的长度为1
	else {
		int mid = (tr[u].l + tr[u].r) >> 1;
		if (mid >= x) modify(u << 1, x, v);
		else modify(u << 1 | 1, x, v);
		pushup(u);//回溯从新求父节点的和
	}
}
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n;
	for (int i = 1; i <= n; i++)cin >> h[i], h[i]++;  // 注意树状数组的下标从  1  开始
	build(1, 1, N-1);
	// 先求前面大的数
	for (int i = 1; i <= n; i++) {
		// 本题是建立树状数组是每个数h[i]是否出现
		// query(N - 1) - query(h[i])是查询数值大小为(h[i],N)中有已经出现数的个数
		// 所以我们要边查询边建立树状数组,这样才可以查询当前数前面的数在(h[i],N)范围出现的个数
		ans[i] += query(1,h[i]+1,N - 1);
		modify(1,h[i], 1);  // add在查询前后进行都可以,因为当前查询设计不到
	}
	
	memset(tr, 0, sizeof tr);  // 重新初始化树状数组,求后面小的数
	build(1, 1, N - 1);
	for (int i = n; i >= 1; i--) {
		// 和前面一样,我们要边查询边建立树状数组,这样才可以查询当前数后面的数在( 1,h[i]-1 ]范围出现的个数
		ans[i] += query(1,1,h[i]-1);
		modify(1,h[i], 1);
	}
	ll res = 0;
	for (int i = 1; i <= n; i++)res += (ll)ans[i] * (ans[i] + 1) / 2; // (ll)防止丢精度
	cout << res;
	return 0;
}

你可能感兴趣的:(蓝桥杯,c++,算法)