【洛谷1966】火柴排队(逆序对)

点此看题面

大致题意:\(a\)\(b\)两个数组,问你要将\(b\)进行多少次相邻两数交换操作,才能使得\(\sum_{i=1}^n(a_i-b_i)^2\)最小。

转化题意

不难猜到,当\(a\)中第\(i\)小的数与\(b\)中第\(i\)小的数配对时,值最小。

证明以\(n=2\)为例(\(n>2\)同理),设\(a_1<a_2,b_1<b_2\),此时只有两种匹配方式:

  1. \(S_1=(a_1-b_1)^2+(a_2-b_2)^2=a_1^2+a_2^2+b_1^2+b_2^2-2a_1b_1-2a_2b_2\)
  2. \(S_2=(a_1-b_2)^2+(a_2-b_1)^2=a_1^2+a_2^2+b_1^2+b_2^2-2a_1b_2-2a_2b_1\)

则用\(S_1\)\(S_2\)得:

\[S_1-S_2=-2(a_1b_1+a_2b_2-a_1b_2-a_2b_1)=-2(a_2-a_1)(b_2-b_1)\]

\[\because a_1<a_2,b_1<b_2,\therefore S_1-S_2<0,\therefore S_1<S_2.\]

原命题得证。

\(rb_i\)\(b_i\)\(b\)序列中第几小的数,则我们可以得出一个序列\(s\),其中\(s_i\)表示\(a\)序列中第\(rb_i\)小的数的位置

那么题意就变成了求要对\(s\)序列进行多少次冒泡排序操作,才可以使\(s\)有序。

逆序对

根据一个著名定理,冒泡排序操作次数即为逆序对个数。

因此我们直接逆序对做即可。

我采用的是归并排序,具体实现见代码。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define X 99999997
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n,s[N+5];
struct data
{
    int p,v;I data(CI x=0,CI y=0):p(x),v(y){}
    I bool operator < (Con data& o) Con {return v^o.v?v<o.v:p<o.p;}
}a[N+5],b[N+5];
class FastIO
{
    private:
        #define FS 100000
        #define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
        #define tn (x<<3)+(x<<1)
        #define D isdigit(c=tc())
        char c,*A,*B,FI[FS];
    public:
        I FastIO() {A=B=FI;}
        Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
        Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
}F;
class MergeSolver//归并排序
{
    private:
        int tot,p[N+5];
        I void Merge(CI l,CI r)
        {
            if(l>=r) return;RI mid=l+r>>1;Merge(l,mid),Merge(mid+1,r);
            RI i=l,j=mid+1,k=l;W(i<=mid&&j<=r)
                s[i]<=s[j]?p[k++]=s[i++]:(p[k++]=s[j++],Inc(tot,mid-i+1));//注意此处统计逆序对
            W(i<=mid) p[k++]=s[i++];W(j<=r) p[k++]=s[j++];
            for(i=l;i<=r;++i) s[i]=p[i];
        }
    public:
        I void Solve() {Merge(1,n),printf("%d",tot);}
}M;
int main()
{
    RI i;for(F.read(n),i=1;i<=n;++i) F.read(a[i].v),a[i].p=i;sort(a+1,a+n+1);
    for(i=1;i<=n;++i) F.read(b[i].v),b[i].p=i;sort(b+1,b+n+1);
    for(i=1;i<=n;++i) s[b[i].p]=a[i].p;return M.Solve(),0;//求出序列s
}

转载于:https://www.cnblogs.com/chenxiaoran666/p/Luogu1966.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值