P1966 火柴排队

知识点: 离散化,逆序对

题面

原题面

分析

首先对计算式进行转化:
\(\ \ \ \ \large \sum\limits_{i=1}^{n}\ (a_i+b_i)^2\)
\(=\large \sum\limits_{i=1}^{n} \ ({a_i}^2+{b_i}^2-2{a_i}{b_i})\)
\(=\large\sum\limits_{i=1}^{n} \ ( {a_i}^2+{b_i}^2) - \sum\limits_{i=1}^{n} \ (2{a_i}{b_i})\)

可以发现, \(\sum\limits_{i=1}^{n} \ ( {a_i}^2+{b_i}^2)\) 不会被排列顺序所影响.
所以 只需要使 \(\sum\limits_{i=1}^{n} \ (2{a_i}{b_i})\) 尽量大即可.

有 一结论:
使 \(b_i\)\(b\) 数组中第 \(i\) 大 , 使 \(a_i\)\(a\) 数组中第 \(i\) 大时,
对应位置相乘后的和最大

  • 证明:
    进行反证, 假设有: \(a_1<a_2 ,\ b_1<b_2\)
    设存在 \(a_1b_1+ a_2b_2 < a_1b_2+ a_2b_1\)
    则有: \(a_1(b_1-b_2) < a_2(b_1-b_2)\)
    因为 \(b_1-b_2<0\) , 则有: \(a_1>a_2\)
    产生矛盾 , 得以反证. 原结论正确

则需要使 两数组中 对应大小的数字 相对应.
可以选择 固定数组 \(b\) , 只改变数组 \(a\) 的顺序.


实现:

先对两数组进行离散化,

由于要将 \(b\) 数组作为标准 , 来改变 \(a\) 数组的顺序
所以对离散化后数组 建立映射 , 把b数组当前排列顺序 , 当做一个升序排列的数列
即: 使 \(b_1\Rightarrow1 , b_2\Rightarrow2 , b_3\Rightarrow3\);

由于都已进行了离散化,
再将 \(a\) 数组中的数进行更改,
按照映射关系 , 为它们赋新值.
即: 使 \(a_1 \Rightarrow b_i \Rightarrow j\)

然后需要将重赋值的 \(a\) 数组进行处理.
由于交换方式为 相邻两元素交换
即冒泡排序,
所以将其变为有序数列的代价 , 即数列中逆序对数.
使用归并排序 / 树状数组求逆序对即可

代码

#include<cstdio>
#include<ctype.h>
#include<algorithm>
#include<map>
#define mid (l+r)/2
#define int long long
const int MARX = 1e6+10;
const int mod = 99999997;
//=============================================================
int n,ans , a[MARX],b[MARX],a1[MARX],b1[MARX],tmp[MARX];
std::map <int,int> s1,s2,le;//离散化 + 映射关系 
//=============================================================
inline int read()
{
	int fl=1,w=0;char ch=getchar();
	while(!isdigit(ch) && ch!='-') ch=getchar();
	if(ch=='-') fl=-1;
	while(isdigit(ch)){w=w*10+ch-'0',ch=getchar();}
	return fl*w;
}
void prepare()
{
	n=read();
	for(int i=1;i<=n;i++) a[i]=a1[i]=read();
	for(int i=1;i<=n;i++) b[i]=b1[i]=read();
	
	std::sort(a1+1,a1+n+1);//离散化a数组 
	for(int i=1;i<=n;i++)
	  if(a1[i]!=a1[i-1])
	    s1[a1[i]]=s1[a1[i-1]]+1;
	
	std::sort(b1+1,b1+n+1);//离散化b数组 
	for(int i=1;i<=n;i++)
	  if(b1[i]!=b1[i-1])
	    s2[b1[i]]=s2[b1[i-1]]+1;
	
	for(int i=1;i<=n;i++)//对离散化后的b数组建立映射关系 
	  if(b[i]!=b[i-1])
	    le[s2[b[i]]]=le[s2[b[i-1]]]+1;
	
	for(int i=1;i<=n;i++) a[i]=le[s1[a[i]]];//按照离散化后的映射关系,重新为a数组赋值 
}
void merge(int l,int r)//归并排序,合并 
{
	int i=l,j=mid+1,k=1;
	for(;i<=mid && j<=r;) 
	  if(a[i]<=a[j]) tmp[k++]=a[i++];
	  else tmp[k++]=a[j++],ans=(ans+(mid-i+1))%mod;//产生逆序对 
	
	while(i<=mid) tmp[k++]=a[i++];
	while(j<=r) tmp[k++]=a[j++];
	for(i=l;i<=r;i++) a[i]=tmp[i-l+1];//重新赋值给a数组 
}
void spile(int l,int r)//归并排序,拆分 
{
	if(l>=r) return ;
	spile(l,mid); spile(mid+1,r);//二分 
	merge(l,r);
}
//=============================================================
signed main()
{
	prepare();
	spile(1,n);
	printf("%lld",ans);
}
posted @ 2019-09-07 18:04  Luckyblock  阅读(129)  评论(1编辑  收藏  举报