[BZOJ2138]stone Hall定理+线段树

假设把每堆石子拆成 Ai A i 个点,每个询问拆成 Ki K i 个点,就相当于每次添加 Ki K i 个点,然后询问此时的最大匹配能增加多少。
通过Hall定理可以判断匹配的合法性。但因为本题的区间没有包含,把询问按照 Li L i 排序, Ri R i 是递增的,在剔除掉没有被任一区间覆盖的石子堆之后,一段询问区间对应的石子也是一段连续的区间,我们不需要判断每个子集,而只需要判断每个区间是否满足Hall定理即可。
Bi B i 是排序后第 i i 个询问所选的石子个数,那么只要满足对于任意 lr l ≤ r ,有

i=lrBii=LlRrAi ∑ i = l r B i ≤ ∑ i = L l R r A i

即可。
证明的话考虑要判断两个中间不相连的询问区间是否满足Hall定理(随之可以推广到多个即子集),假设这两个询问区间对应的石子区间也是不相连的两段,那么整体是否满足就取决于这两对区间是否分别满足。如果对应的石子区间相连,考虑把两个询问区间中间的点补上,而对应的石子区间并没有增加,这对区间满足Hall定理的条件显然比原问题的条件紧,于是得证。
我们考虑把式子化成前缀和的形式: sbrsbl1saRrsaLl1 s b r − s b l − 1 ≤ s a R r − s a L l − 1 。设 Ci=sbiRi,Di=sai1saLi1 C i = s b i − R i , D i = s a i − 1 − s a L i − 1 ,那么就是 CrDl C r ≤ D l
于是我们按时间顺序添加询问,假如当前是给 Bi B i 找最大值,那么显然只有 l<i,r>i l < i , r > i 才会限制,于是我们找到 r>i r > i Ci C i 的最大值, l<i l < i Di D i 的最小值就能得出 Bi B i 的最大取值,然后把 i i 之后的 C,D C , D 加上 Bi B i 即可。
上述操作都用线段树维护。

代码:

#include
#include
#include
#include
#define N 40010
#define ll long long
#define mid (l+r>>1)
using namespace std;
int n,m,a[N],o[N];
struct node
{
    int l,r,k,t,id;
}b[N];
bool cmpl(node p,node q)
{
    return p.l<q.l;
}
bool cmpt(node p,node q)
{
    return p.t<q.t;
}
int read()
{
    int x=0,f=1;char ch=getchar();
    for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=-1;
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
    return x*f;
}
struct tree
{
    int s[N<<2],add[N<<2];
    bool flag;
    int M(int x,int y)
    {
        return flag?max(x,y):min(x,y);
    }
    void update(int v)
    {
        s[v]=M(s[v<<1],s[v<<1|1]);
    }
    void cal(int v,int d)
    {
        s[v]+=d;add[v]+=d;
    }
    void pushdown(int v)
    {
        if(add[v])
        {
            cal(v<<1,add[v]);
            cal(v<<1|1,add[v]);
            add[v]=0;
        }
    }
    void build(int v,int l,int r)
    {
        if(l==r) {s[v]=flag?-a[b[r].r]:-a[b[l].l-1];return ;}
        build(v<<1,l,mid);
        build(v<<1|1,mid+1,r);
        update(v);
    }
    void mdf(int v,int l,int r,int lx,int rx,int c)
    {
        if(l==lx&&r==rx) {cal(v,c);return ;}
        pushdown(v);
        if(rx<=mid) mdf(v<<1,l,mid,lx,rx,c);
        else if(lx>mid) mdf(v<<1|1,mid+1,r,lx,rx,c);
        else mdf(v<<1,l,mid,lx,mid,c),mdf(v<<1|1,mid+1,r,mid+1,rx,c);
        update(v);
    }
    int qry(int v,int l,int r,int lx,int rx)
    {
        if(l==lx&&r==rx) return s[v];
        pushdown(v);
        if(rx<=mid) return qry(v<<1,l,mid,lx,rx);
        if(lx>mid) return qry(v<<1|1,mid+1,r,lx,rx);
        return M(qry(v<<1,l,mid,lx,mid),qry(v<<1|1,mid+1,r,mid+1,rx));
    }
}Tl,Tr;

int main()
{
    n=read();
    int x=read(),y=read(),z=read(),p=read();
    for(int i=1;i<=n;i++)
        a[i]=((ll)(i-x)*(i-x)+(i-y)*(i-y)+(i-z)*(i-z))%p;
    m=read();
    if(!m) return 0;
    b[1].k=read();b[2].k=read();x=read();y=read();z=read();p=read();
    for(int i=3;i<=m;i++)
        b[i].k=((ll)x*b[i-1].k+y*b[i-2].k+z)%p;
    for(int i=1;i<=m;i++)
        b[i].l=read(),b[i].r=read(),b[i].t=i;
    sort(b+1,b+m+1,cmpl);
    for(int i=1;i<=m;i++)
        b[i].id=i;
    b[m+1].l=n+1;
    for(int i=1;i<=m+1;i++)
    {
        int tmp=b[i].l-b[i-1].r-1;
        if(tmp>0) o[b[i].l]+=tmp;
    }
    for(int i=1;i<=n;i++)
        o[i]+=o[i-1];
    for(int i=1;i<=n;i++)
        a[i-o[i]]=a[i];
    for(int i=1;i<=m;i++)
        b[i].l-=o[b[i].l],b[i].r-=o[b[i].r];
    n-=o[n];
    for(int i=1;i<=n;i++)
        a[i]+=a[i-1];
    Tl.flag=0;Tr.flag=1;
    Tl.build(1,1,m);
    Tr.build(1,1,m);
    sort(b+1,b+m+1,cmpt);
    for(int i=1;i<=m;i++)
    {
        int ans=min(Tl.qry(1,1,m,1,b[i].id)-Tr.qry(1,1,m,b[i].id,m),b[i].k);
        printf("%d\n",ans);
        if(b[i].id<m) Tl.mdf(1,1,m,b[i].id+1,m,ans);
        Tr.mdf(1,1,m,b[i].id,m,ans);
    }
    return 0;
}

你可能感兴趣的:(二分图,数据结构,线段树)