洛谷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;
}
`
posted @ 2022-03-08 21:08  Roshin  阅读(33)  评论(0编辑  收藏  举报
-->