题目链接:1214F. Employment
题目大意:给你两个数列(a_1,a_2,dots,a_n)以及(b_1,b_2,dots,b_n),求一个1 ~ n排列(P),使得(sum_{i=1}^{n} |a_i-b_{P_{j}}|)最小,输出最小值及这个排列。
题解:我们先将(a)数组和(b)数组进行排序,并且将(b)数组扩大三倍((b_i-m,b_i,b_i+m)),可以容易证明最优解一定在一段连续的(b)序列中。
那么可以看出(a)和(b)在分配时相对顺序是不会改变的,即对于每一个(b_j)~(b_{j+n-1}),当答案最优时定然为(ans=sum_{i=1}^{n} |a_i-b_{j+i-1}|)。
那么我们就可以很轻松地得到一种(O(n^2))的算法,即每一次枚举(j),然后暴力判断即可。
但是对于数据范围(1 le n le 2cdot 10^5)来说,这个时间复杂度明显是过不了的,那么考虑对该算法进行优化。
我们可以考虑分类讨论来去掉绝对值,那么(ans=sum_{i=1}^n [a_i ge b_{j-i+1}]cdot(a_i-b_{j-i+1})+[a_i < b_{j-i+1}]cdot(b_{j-i+1}-a_i))
很明显,(a_i)和(b_j)在一段连续的区间中的贡献为正,一段连续区间的贡献为负,不超过两段连续区间的贡献为0,那么就可以算出断点用差分来做即可。
code:(qwq,细节真多)
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define ll long long
#define Maxn 200000
struct Node{
ll a;
int id;
friend bool operator <(Node p,Node q){
if(p.a==q.a){
return p.id<q.id;
}
return p.a<q.a;
}
}a[Maxn+5],b[Maxn*3+5];
int m,n;
int c[Maxn+5];
ll f[Maxn*3+5];
int mx(int a,int b){
return a>b?a:b;
}
int mn(int a,int b){
return a<b?a:b;
}
int main(){
scanf("%d%d",&m,&n);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i].a);
a[i].id=i;
}
for(int i=1;i<=n;i++){
scanf("%lld",&b[i].a);
b[i].id=i;
b[i+n]=b[i];
b[i+n].a+=m;
b[i+(n<<1)]=b[i];
b[i+(n<<1)].a-=m;
}
sort(a+1,a+1+n);
sort(b+1,b+1+n*3);
ll ans=(1ll<<60),sum;
int x=0;
for(int i=1,j=1;i<=n;i++){
while(b[j].a<=a[i].a){
j++;
}
f[0]+=a[i].a;
if(j-i+1<=2*n+1){
f[j-i+1]-=(a[i].a<<1);
}
}
for(int j=1,i=1;j<=n*3;j++){
f[mx(0,j-n+1)]-=b[j].a;
while(i<=n&&a[i].a<b[j].a){
i++;
}
if(j-i+2<=n*2+1){
f[j-i+2]+=(b[j].a<<1);
}
f[mn(n*2+2,j+1)]-=b[j].a;
}
sum=f[0];
for(int i=1;i<=2*n+1;i++){
sum+=f[i];
if(sum<ans){
x=i;
ans=sum;
}
}
for(int i=1;i<=n;i++){
c[a[i].id]=b[x+i-1].id;
}
cout<<ans<<endl;
for(int i=1;i<=n;i++){
printf("%d ",c[i]);
}
puts("");
return 0;
}