P1966. 火柴排队

考点

  • 排序

题解

见思路

思路

$$ \sum{\left( a_i-b_i \right) ^2}\rightarrow \sum{{a_i}^2+{b_i}^2-2a_ib_i}\rightarrow \underset{A}{\underbrace{\sum{{a_i}^2+{b_i}^2}}}-\underset{B}{\underbrace{2\sum{a_ib_i}}} $$

由上述式子展开发现,A式其实是不变的;要让整体最小,a、b数组对应位置的元素乘积就要最大。

排序不等式可知,若把两数组排序后,对于每个k,将第k小的ak元素与第k小的bk元素配对可得到最大乘积。

通俗点说,ai在a数组的大小排名应该等于bi在b数组的大小排名。

数组转换法(必须掌握)

由于是比较排名,先对a、b两数组进行离散化,得到如下数据:

a = {1, 3, 4, 2, 5}b = {3, 2, 5, 4, 1}

此时要求数组a转换为数组b需要交换元素的次数。

正常情况下,数组通过下面的映射规律后再排序(只是平常没有感觉)

数组值(值) 1 2 3 4 5
数组下标(键) 1 2 3 4 5

如果此时将数组b作为新的映射规律后:

数组值(值) 1 2 3 4 5
数组下标(键) 3 2 5 4 1

那么数组a在b的映射下,就会变成a = {1 -> 5, 3 -> 1, 4 -> 4, 2 -> 2, 5 -> 3}

a = {5, 1, 4, 2, 3}

接下来就是求此时的数组a排序的交换元素次数,直接累计所有元素的逆序对个数即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 50, mod = 1e8 - 3;
int n, a[maxn], b[maxn], mp[maxn];
int atot, btot, da[maxn], db[maxn];
ll ans, bit[maxn];

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

void add(int x, int v) {
while (x <= n) bit[x] += v, x += lowbit(x);
}

ll query(int x) {
ll res = 0;
while (x > 0) res += bit[x], x -= lowbit(x);
return res;
}

// 离散化
void dis() {
cin >> n;
for (int i = 1; i <= n; ++i) cin >> a[i], da[i] = a[i];
for (int i = 1; i <= n; ++i) cin >> b[i], db[i] = b[i];
sort(da + 1, da + 1 + n), sort(db + 1, db + 1 + n);
atot = unique(da + 1, da + 1 + n) - 1 - da;
btot = unique(db + 1, db + 1 + n) - 1 - db;
for (int i = 1; i <= n; ++i) {
a[i] = lower_bound(da + 1, da + 1 + atot, a[i]) - da;
b[i] = lower_bound(db + 1, db + 1 + btot, b[i]) - db;
}
for (int i = 1; i <= n; ++i) mp[b[i]] = i;
for (int i = 1; i <= n; ++i) a[i] = mp[a[i]];
}

int main() {
dis();
ll ans = 0;
for (int i = n; i >= 1; --i) {
ans = (ans + query(a[i] - 1)) % mod;
add(a[i], 1);
}
cout << ans;
return 0;
}

取巧

本题可以不用像上面一样更改映射,保持默认的映射即可:

数组值(值) 1 2 3 4 5
数组下标(键) 1 2 3 4 5

用结构体数组a、b分别记录原a、b数组每个元素的值val以及元素的下标pos

结构体数组a、b分别对val排序,该操作可以理解为,以默认映射为标准进行对齐

对齐之后,ai和bival在各自原数组内的排名都是一致的;

为满足题意要求,按道理ai和bipos也应该一样;如果不一样,须让a[i].pos朝b[i].pos对齐,

那么设新的映射数组mp[b[i].pos] = a[i].pos,mp排序时的逆序对总数即为答案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 50, mod = 1e8 - 3;
int n, mp[maxn], t[maxn];
ll ans;

struct node {
int val_, pos_;
bool operator<(node &x) { return val_ < x.val_; }
} a[maxn], b[maxn];

void merge(int l, int r) {
if (l == r) return;
int i = l, j = l, mid = (l + r) / 2, k = mid + 1;
merge(l, mid), merge(mid + 1, r);
while (j <= mid && k <= r) {
if (mp[j] <= mp[k]) {
t[i++] = mp[j++];
} else {
t[i++] = mp[k++];
ans = (ans + mid - j + 1) % mod;
}
}
while (j <= mid) t[i++] = mp[j++];
while (k <= r) t[i++] = mp[k++];
for (i = l; i <= r; ++i) mp[i] = t[i];
}

int main() {
cin >> n;
for (int i = 1; i <= n; ++i) cin >> a[i].val_, a[i].pos_ = i;
for (int i = 1; i <= n; ++i) cin >> b[i].val_, b[i].pos_ = i;
sort(a + 1, a + 1 + n), sort(b + 1, b + 1 + n);
for (int i = 1; i <= n; ++i) mp[b[i].pos_] = a[i].pos_;
merge(1, n);
cout << ans;
return 0;
}