Codeforces 1214F. Employment题解

题目链接: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;
}
原文地址:https://www.cnblogs.com/withhope/p/11762259.html