[Luogu] P1966 火柴排队

\(Link\)

Description

涵涵有两盒火柴,每盒装有\(n\)根火柴,每根火柴都有一个高度。 现在将每盒中的火柴各自排成一列, 同一列火柴的高度互不相同, 两列火柴之间的距离定义为:\(\sum (a_i-b_i)^2\)

其中\(a_i\)表示第一列火柴中第\(i\)个火柴的高度,\(b_i\)表示第二列火柴中第\(i\)个火柴的高度。

每列火柴中相邻两根火柴的位置都可以交换,请你通过交换使得两列火柴之间的距离最小。请问得到这个最小的距离,最少需要交换多少次?如果这个数字太大,请输出这个最小交换次数对\(10^8-3\)取模的结果。

Solution

注意到\(\sum(a_i-b_i)^2=\sum{a_i^2}+\sum{b_i^2}-2\sum{a_ib_i}\)。而前面两项都是不会改变的,所以只能考虑改变最后一项\(2\sum{a_ib_i}\)了。

因为我们要最小化\(\sum(a_i-b_i)^2\),所以要最大化\(\sum{a_ib_i}\)。现在给出一个结论:顺序积之和\(\ge\)无序积之和。这是为什么呢?

我们考虑一个子问题:设\(a_1<a_2,b_1<b_2\),那么\(a_1b_1+a_2b_2>a_1b_2+a_2b_1\)。证明就是直接移项,有\(a_2(b_2-b_1)>a_1(b_2-b_1)\),又\(b_1<b_2\),所以\(a_2>a_1\),即\(a_1<a_2\),证毕。

那么对于两个无序的序列\(a_i,b_i\),我们通过类似冒泡排序的思想,总能通过交换若干项,使总贡献越来越大,最后总能变成两个顺序数列,使贡献达到最大。

我们把原来的\(a_i,b_i\)离散化,那么原问题就转化成了另一个问题:对于两个\(1\sim{n}\)的排列\(a_i,b_i\),试通过交换若干项,使得\(\forall{i\in[1,n]},a_i=b_i\)

这个问题也是比较好解决的。我们先把\(a_i\)转化成顺序数列,然后记录一下\(to[a[i]]=i\),表示\(a_i\)这个数变成了\(i\)这个数。再把\(b_i\)进行相应转化,即\(b_i=to[b_i]\)。则现在问题又变成另一个更简单的问题:对于一个无序排列,最少要交换几次,使它变成有序?

这其实就是一个求逆序对啦。因为你要把\(1\sim{n}\)\(n\)个数从小到大依次交换到对应的位置\(a_i=i\)。通过手动模拟可以发现,数\(i\)移到\(a_i\)这个位置需要的交换次数就是\(\sum\limits_{j=1}^{i-1}|a_j>i|\)。那么这就是求逆序对了。具体可以用树状数组实现。

Code

#include <bits/stdc++.h>

using namespace std;

#define lowbit(x) ((x) & (-x))

const int mod = 1e8 - 3;

int n, res, a[100005], b[100005], p[100005], q[100005], to[100005], c[100005];

int read()
{
	int x = 0, fl = 1; char ch = getchar();
	while (ch < '0' || ch > '9') { if (ch == '-') fl = -1; ch = getchar();}
	while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
	return x * fl;
}

void add(int x, int d)
{
	while (x <= n)
	{
		c[x] += d;
		x += lowbit(x);
	}
	return;
}

int query(int x)
{
	int s = 0;
	while (x)
	{
		s += c[x];
		x -= lowbit(x);
	}
	return s;
}

int main()
{
	n = read();
	for (int i = 1; i <= n; i ++ ) a[i] = read(), p[i] = a[i];
	for (int i = 1; i <= n; i ++ ) b[i] = read(), q[i] = b[i];
	sort(p + 1, p + n + 1); sort(q + 1, q + n + 1);
	for (int i = 1; i <= n; i ++ )
	{
		a[i] = lower_bound(p + 1, p + n + 1, a[i]) - p;
		b[i] = lower_bound(q + 1, q + n + 1, b[i]) - q;
	}  
	for (int i = 1; i <= n; i ++ )
		to[a[i]] = i;
	for (int i = 1; i <= n; i ++ )
		b[i] = to[b[i]];
	for (int i = 1; i <= n; i ++ )
	{
		add(b[i], 1);
		res = (res + query(n) - query(b[i])) % mod;
	}
	printf("%d\n", res);
	return 0;
}
posted @ 2020-11-03 10:26  andysj  阅读(66)  评论(1编辑  收藏  举报