P1966 [NOIP2013 提高组] 火柴排队

本文探讨了如何通过计算逆序对数量来确定交换次数,使得两个无序数组的差平方和最小。关键在于理解有序数组的方差最小原理,并利用新建序列记录元素在原数组中的位置信息,避免单纯从排序交换次数考虑。通过实例和冒泡排序或归并排序获取逆序对,实现时间复杂度为O(nLog(n))的解决方案。
摘要由CSDN通过智能技术生成

給兩個無序數組,求出交換最少次數,使得兩個數組對應下標的差平方和最小。

我們知道如果兩個數組都是有序的話,那麼他們的方差就會是最小的。

原因呢?

\sum (a{_{i}} - b{_{i}})^2 = \sum (a{_{i}}^2 + b{_{i}}^2) - 2\sum a{_{i}}b{_{i}}

我們知道,右邊的第一項和是不變的的,我們要想辦法讓第二項最大化,從而令相減結果最小。

那麼為什麼有序的時候,第二項會是最大呢?

a{_{1}} < a{_{2}} \textup{, }b{_{1}} < b{_{2}} \\* a{_{1}}b{_{1}} + a{_{2}}b{_{2}} > a{_{1}}b{_{2}} + a{_{2}}b{_{1}}

所以可以推出有序相乘之和大於無序相乘之和,因此有序會令方差最小化。

上面所提到的有序是一個思路,有序保證了在a數組中第k小的數的下標和在b數組中第k小的數的下標相同,相減就能得出最小方差,這也是上面證明告訴我們的。

更進一步的說,只要原數組a和b中他們雖然不是有序,但是如果他們的下標相同的位置都是在自己序列中的第k小,那麼其實出來的效果和有序計算方差的效果是一樣的,只是計算的次序弄混了。

所以如果我們只是單從排序中要交換的次數來思考答案,就會掉進陷阱裡了。

我給你來一個例子:

A = {4, 1, 3, 5}
B = {4, 1, 3, 5}

很明顯,答案是方差為0,並且無需交換。

但是如果是從排序中的交換次數來進行思考,就會得出很不一樣的答案。

那應該怎麼做呢,其實我們只需要一個新建序列。

新建序列會把A/ B 所有元素在原數組中的位置信息記下來,所以當我們只要獲得a和b的每個元素大小信息的時候,我們就可以跟原數組的下標進行比較,如果一樣說明沒有必要交換並且他們一定是最優配對。

那麼我們先開結構體,給入兩個參數,一個是位置信息,一個是數值信息。

我們對A 和 B兩個數組的值進行排序,得到每個數字的大小排名信息。

新數組我們會以 B的每個元素的下標作為key,並且把A的對應元素的下標作為value

這樣的話我們就可以獲得,A中最小的元素下標是否和B中最小元素下標一樣,如此類推

所以得出不需要交換的條件是 nums[i] = i

需要交換的話意味著nums[i] != i, 也能得出 nums[i] = j and nums[j] = i, where 0 <= i < j

你仔細想想,這個其實就是正二八經的逆序對。

那麼逆序對可以透過冒泡排序或者歸併排序取得,這個就不多講了。

Time Complexity : O(nLog(n))

#include <iostream>
#include <algorithm>
using namespace std;
struct node{
    long long id, val;
}a[100009], b[100009];
int c[100009], temp[100009], n;
long long ans = 0;
bool cmp(node a, node b){
    if(a.val < b.val)return true;
    else return false;
}
void mergeSort(int left, int right){
    if(left >= right)return;
    int mid = (left + right) / 2;
    
    mergeSort(left, mid);
    mergeSort(mid + 1, right);
    
    for(int i = left; i <= right; i++){
        temp[i] = c[i];
    }
    
    int i = left, j = mid + 1;
    for(int curr = left; curr <= right; curr++){
        if(i == mid + 1)c[curr] = temp[j++];
        
        else if(j > right) c[curr] = temp[i++];
        
        else if(temp[i] <= temp[j]) c[curr] = temp[i++];
        
        else{
            ans = (ans + mid + 1 - i) % (100000000 - 3);
            c[curr] = temp[j++];
        }
    }
}
int main(){
    cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> a[i].val;
        a[i].id = i;
    }
    for(int i = 1; i <= n; i++){
        cin >> b[i].val;
        b[i].id = i;
    }
    sort(a + 1, a + n + 1, cmp);
    sort(b + 1, b + n + 1, cmp);
    for(int i = 1; i <= n; i++){
        c[b[i].id] = a[i].id;
    }
    mergeSort(1, n);
    cout << ans;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值