AtCoder Regular Contest 076F: Exhausted? 题解

首先需要知道Hall定理:

设二分图G的左边节点集合为 A A ,右边节点集合为B,那么G存在完美匹配的充
要条件是
对于 XA,XΓ(X) ∀ X ∈ A , ∣ X ∣≤∣ Γ ( X ) ∣
其中 Γ(X) Γ ( X ) 表示集合 B B 中所有与X中至少一点相邻的点的集合

这题显然不能去真正的跑二分图最大匹配,一看就要用Hall定理
也就是我们想要找到一个集合 X X ,使得XΓ(X)最大,这个最大值就是我们的答案
显然我们不能枚举子集,这样是指数级的复杂度
我们考虑将所有的人按照 L L 从小到大排序,然后我们枚举当前选中的集合中左边一半椅子最右边的那个人是谁,也就是从左到右枚举
那么我们肯定在这个人前面选择一些人组成集合
麻烦的是这些人的右端点并不是单调的
我们考虑当前这个人的右端点,那么比他右端点靠右的人肯定全选,因为可以增加人数而不会增加椅子集合的大小
比他右端点靠左的人就比较讲究,一方面选他会增加人数,另一方面也会增加椅子集合的大小,我们会发现后面的东西都是ΔlenΔrank的一个东西求最大值,我们可以考虑维护一棵动态开点线段树,区间的合并原则是右边和左边比较时要减去一个左边的size
大概是这样吧

#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <utility>
#include <cctype>
#include <algorithm>
#include <bitset>
#include <set>
#include <map>
#include <vector>
#include <queue>
#include <deque>
#include <stack>
#include <cmath>
#define LL long long
#define LB long double
#define x first
#define y second
#define Pair pair<int,int>
#define pb push_back
#define pf push_front
#define mp make_pair
#define LOWBIT(x) x & (-x)
#define DEBUG(...) 
using namespace std;

const int MOD=1e9+7;
const LL LINF=2e16;
const int INF=1e9;
const int magic=348;
const double eps=1e-10;
const double pi=3.14159265;

inline int getint()
{
    char ch;int res;bool f;
    while (!isdigit(ch=getchar()) && ch!='-') {}
    if (ch=='-') f=false,res=0; else f=true,res=ch-'0';
    while (isdigit(ch=getchar())) res=res*10+ch-'0';
    return f?res:-res;
}

int n,m;
Pair a[200048];

namespace SegmentTree
{
    struct node
    {
        int left,right,sz;
        int minval;
    }tree[1000048];
    inline void build(int cur,int left,int right)
    {
        tree[cur].left=left;tree[cur].right=right;tree[cur].sz=0;
        tree[cur].minval=INF;
        if (left!=right)
        {
            int mid=(left+right)>>1;
            build(cur<<1,left,mid);build(cur<<1|1,mid+1,right);
        }
    }
    inline int pushup(int val1,int sz,int val2) {return min(val1,val2-sz);}
    inline void Insert(int cur,int pos)
    {
        if (tree[cur].left==tree[cur].right)
        {
            tree[cur].sz++;tree[cur].minval=pos-tree[cur].sz;
            return;
        }
        int mid=(tree[cur].left+tree[cur].right)>>1;
        if (pos<=mid) Insert(cur<<1,pos); else Insert(cur<<1|1,pos);
        tree[cur].minval=pushup(tree[cur<<1].minval,tree[cur<<1].sz,tree[cur<<1|1].minval);
        tree[cur].sz=tree[cur<<1].sz+tree[cur<<1|1].sz;
    }
    inline int query_rank(int cur,int pos)
    {
        if (tree[cur].left==tree[cur].right) return tree[cur].sz;
        int mid=(tree[cur].left+tree[cur].right)>>1;
        if (pos<=mid) return query_rank(cur<<1,pos); else return query_rank(cur<<1|1,pos)+tree[cur<<1].sz;
    }
    inline Pair query_min(int cur,int left,int right)
    {
        if (left>right) return mp(INF,0);
        if (left<=tree[cur].left && tree[cur].right<=right) return mp(tree[cur].minval,tree[cur].sz);
        Pair res1=mp(INF,0),res2=mp(INF,0);int mid=(tree[cur].left+tree[cur].right)>>1;
        if (left<=mid) res1=query_min(cur<<1,left,right);
        if (mid+1<=right) res2=query_min(cur<<1|1,left,right);
        Pair res;res.x=pushup(res1.x,res1.y,res2.x);res.y=res1.y+res2.y;
        return res;
    }
    inline int query_max(int cur)
    {
        if (tree[cur].left==tree[cur].right) return tree[cur].left;
        if (tree[cur<<1|1].sz) return query_max(cur<<1|1); else return query_max(cur<<1);
    }
}

int main ()
{
    int i;n=getint();m=getint();
    for (i=1;i<=n;i++) a[i].x=getint(),a[i].y=getint();
    sort(a+1,a+n+1);
    SegmentTree::build(1,0,m);
    int ans=0,curans;
    for (i=1;i<=n;i++)
    {
        SegmentTree::Insert(1,m+1-a[i].y);
        if (a[i].x+1>=a[i].y) ans=max(ans,i-m);
        int ck=SegmentTree::query_rank(1,m+1-a[i].y);
        curans=a[i].x+(m+1-a[i].y)-ck;
        Pair res=SegmentTree::query_min(1,m+1-a[i].y+1,m+1-a[i].x-2);
        curans=min(curans,a[i].x+res.x-ck);
        ans=max(ans,-curans);
        ans=max(ans,i-min(a[i].x+SegmentTree::query_max(1),m));
    }
    printf("%d\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值