• 【BZOJ1135】LYZ(POI2009)-线段树+Hall定理


    测试地址:LYZ
    题目大意:溜冰俱乐部配备有1~n号的溜冰鞋各k双,脚为x号的人可以穿x~x+d号的溜冰鞋,有m次操作,每次操作给出两个数ri,xi,表示有xi个脚为ri号的人来到了俱乐部(xi可能为负,表示走了|xi|个人),问每次操作之后,有没有足够的鞋子分配给当前在俱乐部的所有人。
    做法:本题需要用到线段树+Hall定理。
    首先,不难想出一个非常暴力的想法:将每个号的溜冰鞋拆成k个点,每个人向能穿的鞋子连边,然后做二分图最大匹配来判定有没有完备匹配(就是每个人都匹配了一双鞋子)。然而这个复杂度大到不用写出来都知道肯定会爆炸了,我们需要加速这个判断的过程。
    我们想到判定二分图有没有完备匹配的Hall定理(实际上我也是今天学的……),这个定理简单来说如下:
    一个二分图被分成两个点集X,Y,存在一个匹配使得X中的每个点都被匹配的充要条件是,对于任意一个X的子集S,都直接连接Y中的大于等于|S|个点。
    简单证明一下这个定理,首先必要性显然(要不然你是怎么匹配上的?),至于充分性,采用反证法,如果存在一个二分图满足Hall定理的条件,但是没有完备匹配,那么我们可以找到X中的一个未被匹配到的点,因为二分图满足Hall定理的条件,所以这个点一定连接至少一个Y中的点,如果这个Y中的点没有被匹配到,那么和假设矛盾,反之则可以找到这个点在X中的匹配点,现在X中我们选定了两个点,根据Hall定理的条件,它们一定连接至少两个Y中的点,这个多出来的点可能连接这两个点中的某一个,但是都不重要,如果连接到的点没有被匹配到,那么找到增广路,和假设矛盾,否则又找到这个点在X中的匹配点……如此这般,我们最后一定能推翻我们的假设,所以充分性证毕。
    那么回到这一题,我们怎么样应用这个定理来判断这样的二分图存不存在完备匹配呢?
    我们把人的集合看做X,那么对于任意两个人,如果他们能穿的鞋子没有交集,那么他们肯定只需要各自满足各自的要求即可,如果有交集的话,可以证明脚号码在这两个人的脚号码之间的那些人穿的鞋子肯定也是这些鞋子,那么只有当所有这些人组成的集合都满足Hall定理的条件时,整个鞋子的分配才有可能是合法的。
    这样我们就把子集的条件变成了连续区间的条件,具体来说,如果穿i号鞋的人有xi个,那么存在一个鞋子分配方案的充要条件是:
    对于任意1lrnd,有i=lrxi(r+dl+1)k
    也就是说:
    不存在1lrnd,使得i=lrxi>(r+dl+1)k
    上面那个不等式等价于:
    i=lr(xik)>dk
    那么我们只需对序列(xik)求出最大子段和,如果最大子段和大于dk,就表示不存在合法分配方案,否则就存在一个合法分配方案。而单点修改和求最大子段和是线段树的基本操作,直接上线段树即可,时间复杂度为O((n+m)logn)
    总的来说,这题的思想非常巧妙,从二分图匹配的暴力,再到想到用Hall定理判定,再到将子集条件转化为区间条件,并用线段树解决,每一步都是非常巧妙的,但又有迹可循,学习了。
    以下是本人代码:

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    int n,m;
    ll k,d,sum[800010],lftsum[800010],rhtsum[800010],mxsum[800010];
    
    void buildtree(int no,int l,int r)
    {
        lftsum[no]=rhtsum[no]=mxsum[no]=0;
        if (l==r) {sum[no]=-k;return;}
        int mid=(l+r)>>1;
        buildtree(no<<1,l,mid);
        buildtree(no<<1|1,mid+1,r);
        sum[no]=sum[no<<1]+sum[no<<1|1];
    }
    
    void pushup(int no)
    {
        sum[no]=sum[no<<1]+sum[no<<1|1];
        mxsum[no]=max(max(mxsum[no<<1],mxsum[no<<1|1]),rhtsum[no<<1]+lftsum[no<<1|1]);
        lftsum[no]=max(lftsum[no<<1],lftsum[no<<1|1]+sum[no<<1]);
        rhtsum[no]=max(rhtsum[no<<1|1],rhtsum[no<<1]+sum[no<<1|1]);
    }
    
    void modify(int no,int l,int r,int pos,ll x)
    {
        if (l==r)
        {
            sum[no]+=x;
            lftsum[no]=rhtsum[no]=mxsum[no]=max(sum[no],(ll)0);
            return;
        }
        int mid=(l+r)>>1;
        if (pos<=mid) modify(no<<1,l,mid,pos,x);
        else modify(no<<1|1,mid+1,r,pos,x);
        pushup(no);
    }
    
    int main()
    {
        scanf("%d%d%lld%lld",&n,&m,&k,&d);
    
        buildtree(1,1,n-d);
        for(int i=1;i<=m;i++)
        {
            int r;
            ll x;
            scanf("%d%lld",&r,&x);
            modify(1,1,n-d,r,x);
            if (mxsum[1]>d*k) printf("NIE
    ");
            else printf("TAK
    ");
        }
    
        return 0;
    }
  • 相关阅读:
    A.2.5输入年,月,判断本月有多少天?
    A.1.2九九乘法表
    运算原理有点复杂,不懂。啊!求解释?
    A.1.1第一个输出程序“Hello World!”
    什么是 MVC ?
    要么滚回去,要么拼命
    创建Android的Hello World应用程序
    转载华硕2008年Java面试题
    Java 计算器实现
    Java 使用execute方法执行Sql语句
  • 原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793464.html
Copyright © 2020-2023  润新知