【洛谷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\),此时只有两种匹配方式:
- \(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\)
- \(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
}
待到再迷茫时回头望,所有脚印会发出光芒