【树状数组】Array Optimization by Deque—CF1579E2

Array Optimization by Deque—CF1579E2
方法参考

翻译

实际上,问题 E1 和 E2 没有太多共同之处。你应该将它们视为两个独立的问题。

给定一个整数数组 a [ 1 … n ] = [ a 1 , a 2 , … , a n ] a[1 \ldots n] = [a_1, a_2, \ldots, a_n] a[1n]=[a1,a2,,an]

让我们考虑一个空的双端队列。双端队列是一种数据结构,支持在开头和末尾添加元素。因此,如果双端队列中当前有元素 [ 3 , 4 , 4 ] [3, 4, 4] [3,4,4],将元素 1 1 1 添加到开头,将产生序列 [ 1 , 3 , 4 , 4 ] [\color{red}{1}, 3, 4, 4] [1,3,4,4],将其添加到末尾将产生序列 [ 3 , 4 , 4 , 1 ] [3, 4, 4, \color{red}{1}] [3,4,4,1]

数组的元素按顺序添加到最初为空的双端队列中,从 a 1 a_1 a1 开始,以 a n a_n an 结束。在将每个元素添加到队列之前,可以选择将其添加到开头还是末尾。

例如,如果我们考虑一个数组 a = [ 3 , 7 , 5 , 5 ] a = [3, 7, 5, 5] a=[3,7,5,5],一个可能的操作序列如下:

1. 3 3 3 添加到队列的开头:队列内有序列 [ 3 ] [\color{red}{3}] [3]
2. 7 7 7 添加到队列的末尾:队列内有序列 [ 3 , 7 ] [3, \color{red}{7}] [3,7]
3. 5 5 5 添加到队列的末尾:队列内有序列 [ 3 , 7 , 5 ] [3, 7, \color{red}{5}] [3,7,5]
4. 5 5 5 添加到队列的开头:队列内有序列 [ 5 , 3 , 7 , 5 ] [\color{red}{5}, 3, 7, 5] [5,3,7,5]

找到在整个数组处理过程后队列中逆序对数量的最小可能值。

序列 d d d 中的逆序对是指满足 i < j i < j i<j d i > d j d_i > d_j di>dj 的索引对 ( i , j ) (i, j) (i,j)。例如,数组 d = [ 5 , 3 , 7 , 5 ] d = [5, 3, 7, 5] d=[5,3,7,5] 恰好有两个逆序对,即 ( 1 , 2 ) (1, 2) (1,2) ( 3 , 4 ) (3, 4) (3,4),因为 d 1 = 5 > 3 = d 2 d_1 = 5 > 3 = d_2 d1=5>3=d2 d 3 = 7 > 5 = d 4 d_3 = 7 > 5 = d_4 d3=7>5=d4
输入

第一行包含一个整数 t t t ( 1 ≤ t ≤ 1000 1 \leq t \leq 1000 1t1000) ,表示测试用例的数量。

接下来的 2 t 2t 2t 行包含测试用例的描述。

每个测试用例描述的第一行包含一个整数 n n n ( 1 ≤ n ≤ 2 ⋅ 1 0 5 1 \le n \le 2 \cdot 10^5 1n2105),表示数组大小。描述的第二行包含 n n n 个以空格分隔的整数 a i a_i ai ( − 1 0 9 ≤ a i ≤ 1 0 9 -10^9 \le a_i \le 10^9 109ai109),表示数组的元素。

确保所有测试用例中 n n n 的总和不超过 2 ⋅ 1 0 5 2 \cdot 10^5 2105
输出

输出 t t t 行,每行包含对应测试用例的答案。每个测试用例的答案应该是一个整数,表示执行所描述算法后队列中的逆序对的最小可能数量。
注意

从初始数组 [ 3 , 7 , 5 , 5 ] [3, 7, 5, 5] [3,7,5,5](第一个示例测试用例)得到只包含两个逆序对的队列 [ 5 , 3 , 7 , 5 ] [5, 3, 7, 5] [5,3,7,5] 的一种方法在问题说明中已经描述过。

此外,在这个示例中,你可以通过将原始数组的每个元素简单地放在队列的末尾来得到两个逆序对的答案。在这种情况下,原始序列 [ 3 , 7 , 5 , 5 ] [3, 7, 5, 5] [3,7,5,5],也包含恰好两个逆序对,将不作改变地保留在队列中。

思路

我一开始并没有想到用树状数组,看见逆序对就直接把acwing的模板代码给拉过来了。
然后开心地暴力填充双端队列。
结果,Wrong answer on test 2 🤔,总感觉自己的方法太复杂了。
后来问了一下ChatGPT,发现distance()用在multiset的时间复杂度是 O ( n ) O(n) O(n) 的。嗯,还是别调了,就算把第二个测试点调对这个题还是会 T L E TLE TLE
然后就快乐地去洛谷看了题解🤣。
果然如yxc所说,树状数组的题目只要能想到要用这个方法,代码就很好写。这个题的代码确实很简单。

C o d e Code Code

#include <bits/stdc++.h>
#define int long long
#define sz(a) ((int)a.size())
using namespace std;
using PII = pair<int, int>;
using i128 = __int128;
const int N = 2e5 + 10;

int n;
int a[N], b[N]; // 原数组和离散化后的数组
int tr[N]; // 树状数组

int lowbit(int x) {
	return x & -x;
}

int sum(int x) {
	int res = 0;
	for (int i = x; i; i -= lowbit(i)) {
		res += tr[i];
	}
	return res;
}

void add(int x, int t) {
	for (int i = x; i <= n; i += lowbit(i)) {
		tr[i] += t;
	}
}

void solve() {
	cin >> n;
	for (int i = 1; i <= n; i ++) {
		cin >> a[i];
		b[i] = a[i];
	}
	
	// 初始化树状数组tr[]
	fill(tr, tr + n + 1, 0);
	
	// 离散化,a[] -> b[]
	sort(a + 1, a + n + 1); // 注意要先排序!
	int len = unique(a + 1, a + n + 1) - a - 1;
	for (int i = 1; i <= n; i ++) {
		b[i] = lower_bound(a + 1, a + len + 1, b[i]) - a;
	}
	
	int res = 0;
	for (int i = 1; i <= n; i ++) {
		// 贪心地选择加入队头还是队尾
		// 哪种方法新产生的逆序对少就选择哪种
		// 注意,这种方法记录的逆序对不会重复~
		res += min(sum(b[i] - 1), sum(n) - sum(b[i])); 
		// 把一个b[i]加入树状数组
		add(b[i], 1); 
	}
	
	cout << "       ";
	cout << res << "\n";
}

signed main() {
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int T = 1;
	cin >> T; cin.get();
	while (T --) solve();
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值