模擬賽 怨靈退治 題解(Hall定理+線段樹)

題意:
有 n 羣怨靈排成一排,燐每秒鐘會選擇一段區間,消滅至多 k 只怨靈。
如果怨靈數量不足 k,則會消滅盡量多的怨靈。
燐作爲一隻有特點的貓,它選擇的區間是不會相互包含的。它想要知道它每秒最多能消滅多少怨靈。
要求:在之前每次都消滅盡量多的怨靈的情況下,求第 i 秒最多能消滅的怨靈的數量。

首先,這題可以用網絡流做部分分。
考慮如何判斷是否可行:
有一種顯然的二分圖匹配:把每個詢問放在X部,怨靈放在Y部。
然後,把詢問,怨靈分別拆點,進行區間連邊,做匹配,如果有完美匹配,則可行。

但是,如果直接而匹配,會超時。我們考慮使用Hall定理進行判斷。
Hall定理,就是說,在X部上取任意子集,與其相連的Y部結點都不小於集合大小。
如果我們要取子集,會變得更慢。

首先,把區間排序。
通過分析,我們知道:只要判斷連續區間即可。

在X不連續時:
1、X部中一個點拆出來的點選子集,不如都選,因爲都選不會使Y部大小增加。、
2、類似的,若X部中選的點在Y部中連續,那麼X一定要連續,因爲中間增加不會使Y部大小增加(區間是不會相互包含的)。
3、若X部中選的點在Y部中不連續,那可以拆成多個,拆的前後是等價的。

所以,我們只要快速判斷連續區間就行。
記怨靈數量的前綴和爲 sum[i],區間消滅的怨靈數量的前綴和爲 sumval[i],區間左右端點爲 l[i],r[i]。
那麼我們要求對於任意 \(1\leq L\leq R\leq m\),滿足:

\(sumval[R]-sumval[L-1]\leq sum[r[R]]-sum[l[L]-1]\)
\(sumval[R]-sum[r[R]]\leq sumval[L-1]-sum[l[L]-1]\)

由於sum不變,只有sumval會邊,所以當區間 i 被加入時:
若L-1>=i,R>=i,那麼大小關係不變。
若L-1<i,R<i,那麼大小關係不變。
只有對L-1<i,R>=i,即L<=i,R>=i 的情況產生影響。
所以區間 i 被加入時設結果爲val[i],那麼對於任意 \(1\leq L\leq i\leq R\leq m\),要求 val[i]滿足:

\(sumval[R]+val[i]-sum[r[R]]\leq sumval[L-1]-sum[l[L]-1]\)
\(val[i]\leq sumval[L-1]-sum[l[L]-1]]-(sumval[R]-sum[r[R]])\)

那麼,找到最小的 \(sumval[L-1]-sum[l[L]-1]\),最大的 \(sumval[R]-sum[r[R]]\),相減就是 val[i]的值。(當然,如果 k 小於這個值的話,val[i]就是 k)
使用線段樹,支持區間加,區間最值,即可實現上述操作。
時間複雜度:\(O(mlog_2 m)\)
感覺這個思路非常巧妙。

代碼:

#include <stdio.h>
#include <stdlib.h>
#define inf 2000000000
int sz[100010],px[100010],wz[100010];
int xl[100010],xr[100010],xk[100010];
int cmp(const void*a,const void*b)
{
    return xl[*(int*)a]-xl[*(int*)b];
}
int z1[400010],z2[400010],su[100010];
int l1[400010],l2[400010];
void pushup(int i)
{
    z1[i]=z1[(i<<1)|1];
    z2[i]=z2[(i<<1)|1];
    if(z1[i<<1]<z1[i])
        z1[i]=z1[i<<1];
    if(z2[i<<1]>z2[i])
        z2[i]=z2[i<<1];
}
void pushdown(int i)
{
    l1[i<<1]+=l1[i];
    l1[(i<<1)|1]+=l1[i];
    z1[i<<1]+=l1[i];
    z1[(i<<1)|1]+=l1[i];
    l1[i]=0;
    l2[i<<1]+=l2[i];
    l2[(i<<1)|1]+=l2[i];
    z2[i<<1]+=l2[i];
    z2[(i<<1)|1]+=l2[i];
    l2[i]=0;
}
void jianshu(int i,int l,int r)
{
    if(l+1==r)
    {
        z1[i]=-su[xl[px[l]]-1];
        z2[i]=-su[xr[px[l]]];
        return;
    }
    int m=(l+r)>>1;
    jianshu(i<<1,l,m);
    jianshu((i<<1)|1,m,r);
    pushup(i);
}
void xiugai1(int i,int l,int r,int L,int R,int x)
{
    if(R<=l||r<=L)
        return;
    if(L<=l&&r<=R)
    {
        z1[i]+=x;
        l1[i]+=x;
        return;
    }
    int m=(l+r)>>1;
    pushdown(i);
    xiugai1(i<<1,l,m,L,R,x);
    xiugai1((i<<1)|1,m,r,L,R,x);
    pushup(i);
}
void xiugai2(int i,int l,int r,int L,int R,int x)
{
    if(R<=l||r<=L)
        return;
    if(L<=l&&r<=R)
    {
        z2[i]+=x;
        l2[i]+=x;
        return;
    }
    int m=(l+r)>>1;
    pushdown(i);
    xiugai2(i<<1,l,m,L,R,x);
    xiugai2((i<<1)|1,m,r,L,R,x);
    pushup(i);
}
void xiugai(int n,int j,int x)
{
    xiugai1(1,1,n+1,j+1,n+1,x);
    xiugai2(1,1,n+1,j,n+1,x);
}
int getz1(int i,int l,int r,int L,int R)
{
    if(R<=l||r<=L)
        return inf;
    if(L<=l&&r<=R)
        return z1[i];
    int m=(l+r)>>1;
    pushdown(i);
    int rt1=getz1(i<<1,l,m,L,R),rt2=getz1((i<<1)|1,m,r,L,R);
    if(rt1<rt2)return rt1;
    else return rt2;
}
int getz2(int i,int l,int r,int L,int R)
{
    if(R<=l||r<=L)
        return -inf;
    if(L<=l&&r<=R)
        return z2[i];
    int m=(l+r)>>1;
    pushdown(i);
    int rt1=getz2(i<<1,l,m,L,R),rt2=getz2((i<<1)|1,m,r,L,R);
    if(rt1>rt2)return rt1;
    else return rt2;
}
int getans(int n,int x)
{
    int x1=getz1(1,1,n+1,1,x+1);
    int x2=getz2(1,1,n+1,x,n+1);
    return x1-x2;
}
int main()
{
    freopen("rin.in","r",stdin);
    freopen("rin.out","w",stdout);
    int n,m;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&sz[i]);
        su[i]=su[i-1]+sz[i];
    }
    scanf("%d",&m);
    if(m==0)return 0;
    for(int i=1;i<=m;i++)
        scanf("%d%d%d",&xl[i],&xr[i],&xk[i]);
    for(int i=1;i<=m;i++)
        px[i]=i;
    qsort(px+1,m,sizeof(int),cmp);
    for(int i=1;i<=m;i++)
        wz[px[i]]=i;
    jianshu(1,1,m+1);
    for(int i=1;i<=m;i++)
    {
        int jg=getans(m,wz[i]);
        if(xk[i]<jg)
            jg=xk[i];
        xiugai(m,wz[i],jg);
        printf("%d\n",jg);
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章