[火柴排队]
题目大意:使得题目中所给的和最小的时候的交换次数
做法:
1:如果要使得\(\sum_{i}^{n}(a_i-b_i)^{2}\)最小,也就是\(a_i\)中排第\(i\)大的数要和\(b_i\)中排第\(i\)大的数位置对应
2:由1可知,题目与每个火柴的高度并没有关系,只关心他在这个序列中的相对大小,所以我们新开一个\(Rank\)数组,令\(a_i,b_i\)排完序后,以每个\(a_i\)原来的位置为下标,\(b_i\)原来的位置为值,也就是\(Rank[a[i]] = b[i]\)
3:所以最终状态就是每个\(a_i=b_i\),即\(Rank[i] = i\)
4:这样我们就把题目转化了一下,重新描述一下题目:把一个给定的乱序数列排成一个升序数列的最小次数,这显然是逆序对。
说明:
1:这个东西为什么是逆序对呢?因为根据题意,每次交换相邻的两个,使得数列有序,那么这肯定就是冒泡排序的交换次数吧。
2:那么为什么冒泡排序的交换次数和逆序对的个数相等呢?
证明:
1:我们设数列为\({a_1,a_2,a_3...a_n}\),每个数的产生的逆序对的个数为\({b_1,b_2,b_3...b_n}\)
2:我们从第一个产生逆序对不为\(0\)的数开始考虑(为\(0\)的话说明之前的已经排好了),考虑将这个数排到前面某个位置使得这个数不会产生逆序对,那么这个交换对后面每个\(a_i\)产生的逆序对的数量\(b_i\)不会产生影响,但是把这个数转移到前面的某个位置使得他不产生逆序对的交换次数就等于逆序对的个数。举个栗子:\((3 4 5 1)\):若把\(1\)调到最前面显然要交换\(3\)次。因此每个\(a_i\)移到前面某个位置使得它不产生逆序对,对答案的贡献产生了\(b_i\)的贡献,而且之前每个数的大小位置不发生改变,且同时对后面每个\(a_i\)所产生的逆序对的数量\(b_i\)不会产生影响,所以结论得以证明。
所以我们现在只需要求出\(Rank\)数组中逆序对的数量即可
归并排序
树状数组
#include <cstdio>
#include <iostream>
#include <algorithm>
typedef long long LL;
using namespace std;
const int maxn = 1e5+6,mod = 99999997;
int n,Rank[maxn];LL ans,tree[maxn];
struct qwq{
LL val;int pos;
bool operator < (const qwq &C)const
{
if(val != C.val)
return val < C.val;
else return pos < C.pos;
}
}a[maxn],b[maxn];
int lowbit(int k)
{
return k & -k;
}
void add(int x,int k)
{
for(;x<=n;x+=lowbit(x))
tree[x] += k;
return ;
}
LL sum(int x)
{
LL res = 0;
for(;x;x-=lowbit(x))
res += tree[x];
return res;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i].val);
a[i].pos = i;
}
for(int i=1;i<=n;i++)
{
scanf("%d",&b[i].val);
b[i].pos = i;
}
sort(a+1,a+n+1);sort(b+1,b+n+1);
for(int i=1;i<=n;i++)
Rank[a[i].pos] = b[i].pos;
for(int i=1;i<=n;i++)
{
add(Rank[i],1);
ans += i - sum(Rank[i]);
ans %= mod;
}
printf("%lld",ans);
return 0;
}