P1966 火柴排队

知识点: 离散化,逆序对

题面

原题面

分析

首先对计算式进行转化:
    i=1n (ai+bi)2
=i=1n (ai2+bi22aibi)
=i=1n (ai2+bi2)i=1n (2aibi)

可以发现, i=1n (ai2+bi2) 不会被排列顺序所影响.
所以 只需要使 i=1n (2aibi) 尽量大即可.

有 一结论:
使 bib 数组中第 i 大 , 使 aia 数组中第 i 大时,
对应位置相乘后的和最大

  • 证明:
    进行反证, 假设有: a1<a2, b1<b2
    设存在 a1b1+a2b2<a1b2+a2b1
    则有: a1(b1b2)<a2(b1b2)
    因为 b1b2<0 , 则有: a1>a2
    产生矛盾 , 得以反证. 原结论正确

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


实现:

先对两数组进行离散化,

由于要将 b 数组作为标准 , 来改变 a 数组的顺序
所以对离散化后数组 建立映射 , 把b数组当前排列顺序 , 当做一个升序排列的数列
即: 使 b11,b22,b33;

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

然后需要将重赋值的 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 @   Luckyblock  阅读(129)  评论(1编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示