NOIP2013火柴排队

抛出问题

两个数列,求最少交换次数使得所有同位置的两数差的平方和最小(单个数列中的任意元素互不相同)。

传送门QAQ

解决问题

正解法

不拐弯抹角暴力法了,直接上正解!

一看就知道要把这个式子拆开(根据经验):

\[\sum_{i=1}^n(a_i-b_i)^2=\sum_{i=1}^n(a_i^2+b_i^2-2a_ib_i) \]

发现可变动最终值的项仅为\(ab\),那么我们就将题目转换成了每个相同位置的数乘积和要最大。那接下来怎么办呢?如何使得最终结果最小?这里有一个巧妙的数学结论:

\(a<b,c<d\)时,\(ac+bd>ad+bc\)

证明很简单,我们考虑\(a(c-d)\)\(b(c-d)\),我们马上能得出\(a(c-d)>b(c-d)\)的结论,因此有\(ac+bd>bc+ad\)

那么在两个数列中:\({a_1,a_2,a_3,\cdots ,a_n}\)\({b_1,b_2,b_3,\cdots ,b_n}\),从局部考虑全局,必然要每个数列中第\(i\)大的数与另一个数列的第\(i\)大值处于同一位置(下标相同)。

于是我们就可以考虑它们的排序,对应出\(b\)的原序列每个值应该交换到哪个地方(称之为交换序列)。我们可以考虑这些对应位置的几种情况(要保证交换次数最少):

  • 如果两交换位置\(i,j(i<j)\)交换的地点对应为\(x,y(x<y)\),那么这两个位置就不会产生一次交换;
  • 如果两交换位置\(i,j(i<j)\)交换的地点对应为\(x,y(x>y)\),那么这两个位置就必然要交换一次。

根据这些,我们可以得出最少交换次数为交换序列的逆序对个数。

于是题目迎刃而解。

代码一览

树状数组求逆序对。

#include <cstdio>
#include <algorithm>
#define p 99999997

using namespace std;

int n, c[1000005], seg[1000005];
pair<int, int> a[1000005], b[1000005];

inline int lowbit(int x)
{
    return x & -x;
}

inline void add(int x, int v)
{
    while(x <= n)
    {
        seg[x] += v;
        x += lowbit(x);
    }
}

inline int query(int x)
{
    int ans = 0;
    while(x)
    {
        ans += seg[x];
        x -= lowbit(x);
    }
    return ans;
}

void solve()
{
    int ans = 0;
    for(register int i = n; i; i -= 1)
    {
        ans += query(c[i] - 1);
        if(ans >= p)
            ans -= p;
        add(c[i], 1);
    }
    printf("%d", ans);
}

int main()
{
    scanf("%d", &n);
    for(register int i = 1; i <= n; i += 1)
        scanf("%d", &a[i].first), a[i].second = i;
    for(register int i = 1; i <= n; i += 1)
        scanf("%d", &b[i].first), b[i].second = i;
    sort(a+1, a+n+1);
    sort(b+1, b+n+1);
    for(register int i = 1; i <= n; i += 1)
        c[b[i].second] = a[i].second;
    solve();
    return 0;
}

写在最后

感谢洛谷各位大佬的帮助。
大多数内容为个人智力成果,如需转载,请注明出处,禁止作商业用途传播。
最后,感谢各位的阅读。
话说这题稍微改改就成了某年湖南省选题呗,用FFT来写多好啊

posted @ 2018-11-02 22:29  孤独·粲泽  阅读(166)  评论(0编辑  收藏  举报