1. 程式人生 > 其它 >洛谷P1996 火柴排隊 題解

洛谷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\) 開始不斷調整,即得證同序乘目標值最小

流程

  1. 離散化高度
  2. 因為要同序乘,且序列中各值各不相同,則代表離散化後 \(a,b\) 兩個序列可以一一對映。
    所以設 mp[b[i]] = i ,那麼要讓 \(a\) 中序列調整到對應位置就執行 \(a[i]=mp[a[i]]\)
  3. 可以觀察到,要讓現在的 \(a\) 升序排列,交換次數其實就是歸併排序中求逆序對個數,兩者互相等價
  4. 最後套用一下樹狀陣列求逆序對的模板即可(值域樹狀陣列)。

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;
}
`