题目描述:
给定 2n 个数字 ai,你需要找出一组大小为 n 的匹配,使得最大权值最小。一组匹配 (x, y) 的权值为 (x + y) mod m。
n ≤ 105,m ≤ 109, 0 ≤ ai< m。
看到题目描述第一反应就是二分答案,然而这题其实是个结论题
首先因为要求(x + y) mod m
所以在输入的时候直接取模
考虑如果没有%m的限制,贪心地去做这道题,肯定是依次将最小的和最大的加起来
有了%m的限制乍一看没法贪心,然而实际上%m的部分和不%m的部分是互不干扰的
也就是说因为取模后每个数<m,所以权值会分为两类
一类是两数的和,一类是两数的和(>m)-m,每类权值内部按照上述方法贪心
直觉上肯定是将小的数划为第一类将大的数划为第二类,所以我们先给所有数排个序
具体应该划分在哪呢?
第一点是要保证第二类权值都>m
在此基础上,考虑a<b<c<d<e<f
a,b是第一类,cdef是第二类
如果把a,b改为第二类(假设更改后满足上述条件)(注意一定是每次更改两个数,,我wa了好久)
那么两侧的最大值由max(a+b,c+f-m,d+e-m)变为max(a+f-m,b+e-m,c+d-m)
易证 a+f-m,b+e-m,c+d-m 都小于max(c+f-m,d+e-m)
所以可以发现一定是在满足条件的前提下尽量把更多的数划为第二类
这让我们想到了二分
代码如下
#include<bits/stdc++.h>
using namespace std;
int n,mod,a[300000];
int read()
{
char ch;
int x=0;
ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return x;
}
int check(int st)
{
//下方的循环条件可以看成在正常条件(相当于0为起点)的基础上加了st-1(st-1为起点)
for(int i=st;i<=(n+st)>>1;i++) if(a[i]+a[n-i+st]<mod) return 0;
return 1;
}
int main()
{
cin>>n>>mod;
n=n<<1;
for(int i=1;i<=n;i++)
{
a[i]=read();
a[i]%=mod;
}
sort(a+1,a+1+n);
int l=0,r=n;//1~mid,mid+1~2*n
while(l<r-1)
{
int mid=(l+r)>>1;
if(mid&1) mid-=1;
if(check(mid+1)) r=mid;
else l=mid+1;
}
l=r;//mid始终为偶数,所以l=mid+1一跳就会跳成奇数,r则始终跳偶数
int maxn=0;
//下方的循环条件可以看成在正常条件(相当于0为起点)的基础上加了l(l为起点)
for(int i=l+1;i<=(n+l+1)>>1;i++) maxn=max(maxn,(a[i]+a[n-i+l+1])%mod);
for(int i=1;i<=l;i++) maxn=max(maxn,a[i]+a[l-i+1]);
cout<<maxn;
return 0;
}