NOIP2013火柴排队
抛出问题
两个数列,求最少交换次数使得所有同位置的两数差的平方和最小(单个数列中的任意元素互不相同)。
解决问题
正解法
不拐弯抹角暴力法了,直接上正解!
一看就知道要把这个式子拆开(根据经验):
\[\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来写多好啊)