AGC032E Modulo Pairing

AGC032E

有个 2 ∗ n 2*n 2n的数组 a i a_i ai,你要给其中的数两两配对,使得两个数之和模 M M M的最大值最小。

n ≤ 1 0 5 n\le 10^5 n105

m ≤ 1 0 9 m\le 10^9 m109


简化题意:对于两个数 x x x y y y,若 x + y < M x+y<M x+y<M,则答案和 x + y x+y x+y max ⁡ \max max;若 x + y ≥ M x+y\geq M x+yM,则答案和 x + y − m x+y-m x+ym max ⁡ \max max

先考虑一个简化的问题:如果要两两配对,使得它们的和的最大值最小,怎么做?

一眼就可以看出做法:小的和大的依次配对。两眼可以想出证明:考虑如果存在四个数 a ≤ b ≤ c ≤ d a\le b\le c\le d abcd,假如存在 max ⁡ ( a + d , b + c ) > max ⁡ ( a + c , b + d ) \max(a+d,b+c)>\max(a+c,b+d) max(a+d,b+c)>max(a+c,b+d)。如果 max ⁡ ( a + d , b + c ) = a + d \max(a+d,b+c)=a+d max(a+d,b+c)=a+d,由于 a + d ≤ b + d a+d \le b+d a+db+d矛盾;如果 max ⁡ ( a + d , b + c ) = b + c \max(a+d,b+c)=b+c max(a+d,b+c)=b+c,由于 b + c ≤ b + d b+c\le b+d b+cb+d矛盾。所以 ( a , d ) ( b , c ) (a,d)(b,c) (a,d)(b,c)配对总是比 ( a , c ) ( b , d ) (a,c)(b,d) (a,c)(b,d)优。

其实也可以发现,如果是要让最小值最大,还是小的和大的依次配对最优。

所以如果要求和在一个区间内,最小配最大的方式,可以尽可能地将所有和压在这个区间内。

然后是一个结论:取 x + y < M x+y<M x+y<M x + y ≥ M x+y\ge M x+yM的数,分别在数组的左右两边。

这里搞一个性质:设有 a ≤ b ≤ c ≤ d a\le b \le c\le d abcd,且KaTeX parse error: Undefined control sequence: \and at position 6: a+c<M\̲a̲n̲d̲ ̲b+d\ge M,那么将 ( a , c ) ( b , d ) (a,c)(b,d) (a,c)(b,d)替换成 ( a , b ) ( c , d ) (a,b)(c,d) (a,b)(c,d)会更优。

证明:即证 max ⁡ ( a + c , b + d − M ) ≥ max ⁡ ( a + b , c + d − M ) \max(a+c,b+d-M)\ge \max(a+b,c+d-M) max(a+c,b+dM)max(a+b,c+dM)。显然 a + b ≤ a + c a+b\le a+c a+ba+c,并且因 d < M ≤ M + a d< M\le M+a d<MM+a,所以 c + d − M ≤ a + c c+d-M\le a+c c+dMa+c

于是如果有“相交”的情况,就可以调整成包含。

那么一直调整下去,最后它们就分别在数组的两边了。

枚举这个分界点,然后两边以最小配最大原则来搞。这样是 O ( n 2 ) O(n^2) O(n2)的,但是注意到,如果合法,两边的最大值是随着这个分界点递增而递增的;分界点太大则左半边不合法,分界点太小则右半边不合法。于是可以二分解决。


using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 200010
int n,m;
int a[N];
int ans;
int calcf(int l,int r){
	int f=0;
	for (int i=1;l+i-1<r-i+1;++i){
		if (a[l+i-1]+a[r-i+1]>=m)
			return -1;
		f=max(f,a[l+i-1]+a[r-i+1]);
	}
	return f;
}
int calcg(int l,int r){
	int g=0;
	for (int i=1;l+i-1<r-i+1;++i){
		if (a[l+i-1]+a[r-i+1]<m)
			return -1;
		g=max(g,a[l+i-1]+a[r-i+1]);
	}
	return g;
}
void work(){
	ans=m-1;
	int l=0,r=n/2;
	while (l<=r){
		int mid=l+r>>1;
		int f=calcf(1,mid*2),g=calcg(mid*2+1,n);
		if (f==-1)
			r=mid-1;
		else if (g==-1)
			l=mid+1;
		else{
			ans=min(ans,max(f,g-m));
			r=mid-1;
		}
	}
}
int main(){
	freopen("in.txt","r",stdin);
	scanf("%d%d",&n,&m);
	n*=2;
	for (int i=1;i<=n;++i)
		scanf("%d",&a[i]);
	sort(a+1,a+n+1);
	work();
	printf("%d\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值