洛谷P1996 火柴排隊 題解
阿新 • • 發佈:2022-03-08
題目描述
輸入 \(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; } `