Codeforces981 F. Round Marriage(二分+Hall定理)(有疑点未解决

题意:

有一个长度为L的环[0,L-1]
有n个新郎和n个新娘在环上,
第i个新郎的位置是顺时针a[i]
第i个新娘的位置是顺时针b[i]
每个新郎可以和找一个新娘匹配,
新郎和新娘都只能被匹配一次.
问匹配完n对之后,新郎和新娘之间的距离最大值最小为多少

数据范围:n<=2e5,L<=1e9,0<=a(i),b(i)<L

解法:
新郎和新娘显然能建出二分图,
最大值最小化显然二分,
这题就是二分最大值然后建图,然后判断是否存在完美匹配

用Hall定理判断的话就不用真的建图计算最大匹配了.
对a[]和b[]从小到大排序,
那么对于每个新郎a[i],能够匹配的新娘一定是b[]的某一段[l,r]

对于任意两个新郎i和j,
设i<j,首先L[i]<=L[j],R[i]<=R[j],(先不管是环)
如果两者对应的新娘区间不相交,那么和单独判断i和j的[l,r]相同,
因此只考虑相交情况,即R[i]>=L[j],
因为相交,那么选择[i,j]这一段的所有新郎肯定更劣,
而Hall定理就需要判断最劣情况,因此对新郎直接取连续的一段就行了.

那么问题变为判断是否对于所有的i和j(i<=j),满足:
j-i+1<=R[j]-L[i]+1,移项变为j-R[j]<=i-L[i]

还要考虑环的情况:
设新郎i对应的左右端点分别是L[i],R[i]
1.左端点跨过起点
2.不跨过
3.右端点跨过起点
因此将b[]向左和右再复制一份,分别表示情况1和情况3,
注意复制出的b[]的值需要改动,左边的减掉L,右边的加上L

重要信息:
1.如果i<j,那么L[i]<=L[j],R[i]<=R[j],
也就是说L[i]和R[i]是满足单调性的,
因此从1到n遍历i的时候,可以用双指针维护L[i]和R[i].
2.check就是判断是否存在j-R[j]>i-L[i],(i<=j),若存在则不合法
开一个变量,维护j左边i-L[i]的最小值就行了




疑惑的点:
1.当mid>=(L+1)/2的时候,有可能左端点越过起点,右端点也越过起点,这样应该会发生重复
2.我代码里面的check:
int check(int mid){
    int l=0,r=n*3+1;
    int mi=1e18;
    for(int i=1;i<=n;i++){
        while(a[i]-mid>b[l])l++;
        while(a[i]+mid<b[r])r--;
        if(l>r)return 0;
        mi=min(mi,i-l);
        if(i-r>mi)return 0;
        r++;
    }
    return 1;
}
每次for的最后r++,
遍历到下一个的时候利用while(a[i]+mid<b[r])r--;修正
但是直接用whlie(a[i]+mid>=b[r+1])r++;就不行,很迷
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=2e5+5;
int a[maxm];
int b[maxm*3];
int n,L;
int check(int mid){
    int l=0,r=n*3+1;
    int mi=1e18;
    for(int i=1;i<=n;i++){
        while(a[i]-mid>b[l])l++;
        while(a[i]+mid<b[r])r--;
        if(l>r)return 0;
        mi=min(mi,i-l);
        if(i-r>mi)return 0;
        r++;
    }
    return 1;
}
signed main(){
    ios::sync_with_stdio(0);
    cin>>n>>L;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++)cin>>b[i];
    //
    for(int i=1;i<=n;i++){
        b[i+n]=b[i]-L;//左边
        b[i+n*2]=b[i]+L;//右边
    }
    //
    sort(a+1,a+1+n);
    sort(b+1,b+1+n*3);
    b[0]=-1e18;
    b[n*3+1]=1e18;
    //
    int l=0,r=(L+1)/2;
    int ans=L;
    while(l<=r){
        int mid=(l+r)/2;
        if(check(mid))ans=mid,r=mid-1;
        else l=mid+1;
    }
    cout<<ans<<endl;
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值