洛谷P1996 火柴排队 题解
题目描述
输入 \(n\) 和 \(a,b\) 两个长度为 \(n\) 的序列。\(a,b\) 序列相邻两项可以互相交换。
求使 \(\Sigma_i^n(a_i-b_i)^2\) 为最小值的最小交换次数,答案对 \(1e8-3\) 取模。
思路
\(\Sigma_i^n(a_i-b_i)^2\) 化简后即求 \(\Sigma(a_i\times b_j)\) 的最小值
核心
排序不等式:对两个相同长度的序列,求上述值的最小值,有一个重要结论:
- 同序乘 ≥ 乱序乘 ≥ 逆序乘
证明:
- 两序列按从小到大排序
- 求证,\(a_i*b_i + a_{i+1}*b_{i+1} ≥ a_i*b_{i+1} + a_{i+1}*b_i\)
- 移项得证 \(b_{i + 1}*(a_{i+1}-a_i)≥b_i*(a_{i+1}-a_i)\)
- 从 \(n\) 开始不断调整,即得证同序乘目标值最小
流程
- 离散化高度
- 因为要同序乘,且序列中各值各不相同,则代表离散化后 \(a,b\) 两个序列可以一一映射。
所以设mp[b[i]] = i
,那么要让 \(a\) 中序列调整到对应位置就执行 \(a[i]=mp[a[i]]\) 。 - 可以观察到,要让现在的 \(a\) 升序排列,交换次数其实就是归并排序中求逆序对个数,两者互相等价
- 最后套用一下树状数组求逆序对的模板即可(值域树状数组)。
C++ Code
#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int N = 1e5 + 10, p = 1e8 - 3;
int tr[N], a[N], b[N], n, mp[N];
vector<int> va, vb;
int lowbit(int x){
return x & -x;
}
void add(int x, int c){ // 单点修改
for(int i = x; i <= n; i += lowbit(i))
tr[i] += c;
}
int ask(int x){ // 查询区间 [1, x]
int res = 0;
for(int i = x; i; i -= lowbit(i))
res += tr[i];
return res;
}
int get(int x, int t){
if(!t)
return lower_bound(va.begin(), va.end(), x) - va.begin() ;
else
return lower_bound(vb.begin(), vb.end(), x) - vb.begin() ;
}
int main(){
cin >> n;
for(int i = 1; i <= n; i++){
cin >> a[i];
va.pb(a[i]);
}
for(int i = 1; i <= n; i++){
cin >> b[i];
vb.pb(b[i]);
}
sort(va.begin(), va.end());
va.erase(unique(va.begin(), va.end()), va.end());
sort(vb.begin(), vb.end());
vb.erase(unique(vb.begin(), vb.end()), vb.end());
for(int i = 1; i <= n; i++){
a[i] = get(a[i], 0);
b[i] = get(b[i], 1);
mp[b[i]] = i;
}
for(int i = 1; i <= n; i++)
a[i] = mp[a[i]];
long long res = 0;
for(int i = 1; i <= n; i++){
res = (res + ask(n) - ask(a[i])) % p;
add(a[i], 1);
}
cout << res << endl;
return 0;
}
`