CF1601C Optimal Insertion

CF1601C Optimal Insertion

题目

题目描述

You are given two arrays of integers $ a_1, a_2, \ldots, a_n $ and $ b_1, b_2, \ldots, b_m $ .

You need to insert all elements of $ b $ into $ a $ in an arbitrary way. As a result you will get an array $ c_1, c_2, \ldots, c_{n+m} $ of size $ n + m $ .

Note that you are not allowed to change the order of elements in $ a $ , while you can insert elements of $ b $ at arbitrary positions. They can be inserted at the beginning, between any elements of $ a $ , or at the end. Moreover, elements of $ b $ can appear in the resulting array in any order.

What is the minimum possible number of inversions in the resulting array $ c $ ? Recall that an inversion is a pair of indices $ (i, j) $ such that $ i < j $ and $ c_i > c_j $ .

输入格式

Each test contains multiple test cases. The first line contains the number of test cases $ t $ ( $ 1 \leq t \leq 10^4 $ ). Description of the test cases follows.

The first line of each test case contains two integers $ n $ and $ m $ ( $ 1 \leq n, m \leq 10^6 $ ).

The second line of each test case contains $ n $ integers $ a_1, a_2, \ldots, a_n $ ( $ 1 \leq a_i \leq 10^9 $ ).

The third line of each test case contains $ m $ integers $ b_1, b_2, \ldots, b_m $ ( $ 1 \leq b_i \leq 10^9 $ ).

It is guaranteed that the sum of $ n $ for all tests cases in one input doesn't exceed $ 10^6 $ . The sum of $ m $ for all tests cases doesn't exceed $ 10^6 $ as well.

输出格式

For each test case, print one integer — the minimum possible number of inversions in the resulting array $ c $ .

题意翻译

题目大意

给定两个序列 a , b a,b a,b,长度分别为 n , m ( 1 ≤ n , m ≤ 1 0 6 ) n,m(1\leq n,m\leq 10^6) n,m(1n,m106)。接下来将 b b b 中的所有元素以任意方式插入序列 a a a任意位置,请找出一种插入方式使结果序列中的逆序对数量最小化,并输出这个最小值。

关于插入:任意方式插入任意位置的示例如下。

例如 a = { 1 , 2 , 3 , 4 } , b = { 4 , 5 , 6 } a=\{1,2,3,4\},b=\{4,5,6\} a={1,2,3,4},b={4,5,6},则 c = { 4 , 1 ‾ , 5 , 2 ‾ , 3 ‾ , 4 ‾ , 6 } , { 1 ‾ , 2 ‾ , 6 , 5 , 3 ‾ , 4 , 4 ‾ } … c=\{4,\underline1,5,\underline2,\underline3,\underline4,6\},\{\underline1,\underline2,6,5,\underline3,4,\underline4\}\dots c={4,1,5,2,3,4,6},{1,2,6,5,3,4,4} 均为合法的插入方式。但你不能修改 a a a 的顺序。

输入格式

本题多测(注意多测不清空爆零两行泪

第一行给定一个正整数 t   ( 1 ≤ t ≤ 1 0 4 ) t\ (1\leq t\leq 10^4) t (1t104) 表示数据组数.

接下来对于每组数据,第一行两个整数 n , m   ( 1 ≤ n , m ≤ 1 0 6 ) n,m\ (1\leq n,m\leq 10^6) n,m (1n,m106) 分别表示 a , b a,b a,b 的长度。

第二行包括 n n n 个整数,表示 a a a

第三行包括 m m m 个整数,表示 b b b

保证 1 ≤ a i , b i ≤ 1 0 9 ,   1 ≤ ∑ n , ∑ m ≤ 1 0 6 1\leq a_i,b_i\leq 10^9,\ 1\leq \sum n,\sum m\leq 10^6 1ai,bi109, 1n,m106

输出格式

对于每组数据一行一个整数,表示最小逆序对数。

输入输出样例

输入 #1

3
3 4
1 2 3
4 3 2 1
3 3
3 2 1
1 2 3
5 4
1 3 5 3 1
4 3 6 1

输出 #1

0
4
6

说明/提示

Below is given the solution to get the optimal answer for each of the example test cases (elements of $ a $ are underscored).

  • In the first test case, $ c = [\underline{1}, 1, \underline{2}, 2, \underline{3}, 3, 4] $ .
  • In the second test case, $ c = [1, 2, \underline{3}, \underline{2}, \underline{1}, 3] $ .
  • In the third test case, $ c = [\underline{1}, 1, 3, \underline{3}, \underline{5}, \underline{3}, \underline{1}, 4, 6] $ .

思路

引理:一定存在一种最优情况,使得 b b b c c c中的顺序是单调递增的,即 b b b排序后从左到右插入 a a a得到 c c c.

证明:

假设我们有序列 A B C ABC ABC,我们有 x < y x < y x<y且已知将 x x x插入到 B , C B,C B,C之间最优,我们将 y y y插入到 A , B A,B A,B A , C A,C A,C之间.

显然,对于两种方案, y y y A , C A,C A,C两段产生的逆序对数量都是一样的.

B B B段中大于 x x x的数字有 g 1 g_1 g1个,小于 x x x的数有 s 1 s_1 s1个,则 s 1 ≥ g 1 s_1 \ge g_1 s1g1.(因为 x x x插入到 B C BC BC之间最优)

B B B段中大于 y y y的数字有 g 2 g_2 g2个,小于 y y y的数有 s 2 s_2 s2个,则 g 2 ≤ g 1 , s 2 ≥ s 1 g_2 \le g_1,s_2\ge s_1 g2g1,s2s1.(因为 y > x y>x y>x)

所以 s 2 ≥ g 2 s_2 \ge g_2 s2g2,又因为 y y y x x x之前会产生一个 ( y , x ) (y,x) (y,x)的逆序对,所以 y y y放在 A B AB AB之间不如放在 B C BC BC之间优.

综上, x x x插入的位置之后一定存在一个位置,使得 y y y放在该位置后产生最少的逆序对.

证毕.

所以,我们求一个 p o s i pos_i posi表示 b i b_i bi(排序后)插入到 a i a_i ai之后(当 p o s i pos_i posi等于 0 0 0时,插入到 a a a的最前端).

可知, p o s pos pos是一个单调递增的数组.

因此,我们设 s o l v e ( l 1 , r 1 , l 2 , r 2 ) solve(l_1,r_1,l_2,r_2) solve(l1,r1,l2,r2)求解将 b l 1 … r 1 b_{l_1\ldots r_1} bl1r1插入到 a l 2 … r 2 a_{l_2\ldots r_2} al2r2中的答案.

我们取 m i d = 1 2 ( l 1 + r 1 ) mid = \frac 12 (l_1+r_1) mid=21(l1+r1),用 O ( r 2 − l 2 ) O(r_2-l_2) O(r2l2)的时间求出 p o s m i d pos_{mid} posmid,然后递归: s o l v e ( l 1 , m i d − 1 , l 2 , p o s m i d ) solve(l_1,mid-1,l_2,pos_{mid}) solve(l1,mid1,l2,posmid) s o l v e ( m i d + 1 , r 1 , p o s m i d , r 2 ) solve(mid+1,r_1,pos_{mid},r_2) solve(mid+1,r1,posmid,r2).

得到 p o s pos pos之后,树状数组求逆序对即可.

递归次数不超过 O ( m ) O(m) O(m), s o l v e solve solve的时间复杂度即 O ( n log ⁡ m ) O(n \log m) O(nlogm),树状数组求逆序对的时间复杂度为 O ( ( n + m ) log ⁡ ( n + m ) ) O((n+m)\log(n+m)) O((n+m)log(n+m)).

代码

用VScode观看体验更佳

#include <iostream>
#include <cstdio>
#include <algorithm>

//#define int long long
typedef long long ll;

int read() {
	int re = 0;
	char  c = getchar();
	bool negt = false;
	while(c < '0' ||c > '9')
		negt |= (c == '-') , c = getchar();
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0' , c = getchar();
	return negt ? -re : re;
}

const int N = 1000010;

struct TreeArray {
#define lowbit(_) ((_) & -(_))
	ll a[N * 2];//注意数组大小
	int n;
	void set(int n_) {
		n = n_;
		for(int i = 0 ; i <= n ; i++)a[i] = 0;
	}
	void change(int pos , int dat) {
		if(pos == 0)return;
		for( ; pos <= n ; pos += lowbit(pos))a[pos] += dat;
	}
	ll GetSum(int r) {
		ll sum = 0;
		for( ; r > 0 ; r -= lowbit(r))sum += a[r];
		return sum;
	}
	ll GetSum(int l , int r) {
		return GetSum(r) - GetSum(l - 1);
	}
};

int n , m;
int a[N] , b[N];
int pos[N];

void discretize() {//离散化
	static int tmp[N * 2];
	int siz = 0;
	for(int i = 1 ; i <= n ; i++)tmp[++siz] = a[i];
	for(int i = 1 ; i <= m ; i++)tmp[++siz] = b[i];
	std::sort(tmp + 1 , tmp + siz + 1);
	siz = std::unique(tmp + 1 , tmp + siz + 1) - tmp - 1;
	for(int i = 1 ; i <= n ; i++)
		a[i] = std::upper_bound(tmp + 1 , tmp + siz + 1 , a[i]) - tmp - 1;
	for(int i = 1 ; i <= m ; i++)
		b[i] = std::upper_bound(tmp + 1 , tmp + siz + 1 , b[i]) - tmp - 1;
}


#define divideOptimize 1
#if divideOptimize
int pre , suf;
TreeArray statis;
void divide(int l1 , int r1 , int l2 , int r2) {
	if(l1 > r1)return;
	int mid = (l1 + r1) / 2;
	pos[mid] = l2;

    ll inversionNum = (1ll << 60);
    suf = pre = 0;
    for(int i = l2 ; i <= r2 ; i++)suf = suf + (a[i] < b[mid] && i != 0);
    for(int i = l2 ; i <= r2 ; i++) {
        if(inversionNum > suf + pre)
            inversionNum = suf + pre , pos[mid] = i - 1;
        pre += (a[i] > b[mid] && i != 0);
        suf -= (a[i] < b[mid] && i != 0);
    }
    if(inversionNum > suf + pre)
        inversionNum = suf + pre , pos[mid] = r2;

	divide(l1 , mid - 1 , l2 , pos[mid]);
	divide(mid + 1 , r1 , pos[mid] , r2);
}
#else
TreeArray pre , suf;//这里不小心写了个log^2的分治函数,T飞
void divide(int l1 , int r1 , int l2 , int r2) {
	if(l1 > r1)return;
	int mid = (l1 + r1) / 2;
	pos[mid] = l2;

	pre.change(a[l2] , 1) , suf.change(a[l2] , -1);
	int inversionNum = pre.GetSum(b[mid] + 1 , n + m) + suf.GetSum(b[mid] - 1);
	for(int i = l2 + 1 ; i <= r2 ; i++) {
		pre.change(a[i] , 1) , suf.change(a[i] , -1);
		int newNum = pre.GetSum(b[mid] + 1 , n + m) + suf.GetSum(b[mid] - 1);
		if(newNum < inversionNum) {
			inversionNum = newNum;
			pos[mid] = i;
		}
	}

	for(int i = l2 ; i <= r2 ; i++)pre.change(a[i] , -1) , suf.change(a[i] , 1);

	divide(l1 , mid - 1 , l2 , pos[mid]);
	pre.change(a[pos[mid]] , -1) , suf.change(a[pos[mid]] , 1);
	divide(mid + 1 , r1 , pos[mid] , r2);
}
#endif

void solve() {
	n = read() , m = read();
	for(int i = 1 ; i <= n ; i++)a[i] = read();
	for(int i = 1 ; i <= m ; i++)b[i] = read();

	discretize();
	std::sort(b + 1 , b + m + 1);

#if !divideOptimize
	suf.set(n + m) , pre.set(n + m);
	for(int i = 1 ; i <= n ; i++)
		suf.change(a[i] , 1);
#endif
	divide(1 , m , 0 , n);

	statis.set(n + m);//统计逆序对数量
	ll ans = 0;
	int j = 1;
	for(int i = 1 ; i <= n ; i++) {
		while(j <= m && pos[j] < i)ans += statis.GetSum(b[j] + 1 , n + m) , statis.change(b[j] , 1) , ++j;
		ans += statis.GetSum(a[i] + 1 , n + m) , statis.change(a[i] , 1);
	}
	while(j <= m) ans += statis.GetSum(b[j] + 1 , n + m) , statis.change(b[j] , 1) , ++j;
	printf("%lld\n" , ans);
}
signed main() {
	int T = read();
	while(T--)
		solve();
	return 0;
}
/*
1
7 1
13 2 6 4 12 7 11
10

*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值