2019牛客国庆集训派对day3 Grid(线段树+小容斥)

题意:一个二维矩阵,最开始每个点都独自属于自身连通块,接下来有两种操作,一种是将行号属于 i ∈ [ a , b ] i\in[a,b] i[a,b]的画一条横线(即 1 < = j < m , ( i , j ) 连 接 ( i , j + 1 ) 1<=j<m,(i,j)连接(i,j+1) 1<=j<m,(i,j)(i,j+1))。
第二种是画竖线,让你在每次操作之后输出连通块的个数。

假设矩阵大小为 n ∗ m n*m nm,已经画了的横线的个数为x,竖线的个数为y。

  1. 如果x=0,那么答案就是 ( m − y ) ∗ n + y (m-y)*n+y (my)n+y
  2. 如果y=0,那么答案就是 ( n − x ) ∗ m + x (n-x)*m+x (nx)m+x
  3. 如果都不为0,那么答案就是 n ∗ m − x ∗ m − y ∗ n + x ∗ y + 1 n*m-x*m-y*n+x*y+1 nmxmyn+xy+1,画x条横线就会减少 x ∗ m x*m xm个连通块(同时产生x个新连通块),y条竖线就会减少 y ∗ n y*n yn个点(同时产生y个新连通块),但是这两者是有重叠部分的(故只会产生一个新的连通块),因为横线和竖线都是从起点直接划到终点的,故x条横线就会与y条竖线两两相交产生 x ∗ y x*y xy个点,同时横线与竖线的部分组成了一个新的连通块。

所以说只要可以维护横线,竖线的数量这道题就可以解决了。
如果直接开一个长度为 1 0 9 10^9 109的线段树(维护横线),是不是只要将线段树对应的端点看做对应的区间,如果将行号属于 [ l , r ] [l,r] [l,r]范围的画横线,那线段树上该端点处横线的数量就会变成 r − l + 1 r-l+1 rl+1,同时注意到该题并不需要查询操作,每次只是看线段树的首节点处线段的数量,故不需要标记下传,我们不关心更低层次的数量。

但是肯定开不了这么大的空间,并且注意到值域区间虽然很大,但操作次数很少,所以可以动态开点线段树优化空间,而且需要注意的是在更新的时候如果该线段树上该点代表的区间已经全部画线了则可直接返回,避免造成空间的多开销,实测不加该话会MLE。

也可离散化。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn=1e5+7;

struct Tree{
    int lc,rc,sum;
}a[maxn*28];//线段树内存池;
int tot;
int build(){
    int k=++tot;
    a[k].lc=a[k].rc=a[k].sum=0;
    return k;
}

void insert(int p,int l,int r,int L,int R){
    if(a[p].sum==r-l+1) return ;
    if(l>=L&&r<=R){
        a[p].sum=r-l+1;
        return ;
    }
    int mid=(l+r)>>1;
    if(L<=mid){
        if(!a[p].lc) a[p].lc=build();
        insert(a[p].lc,l,mid,L,R);
    }
    if(R>mid){
        if(!a[p].rc) a[p].rc=build();
        insert(a[p].rc,mid+1,r,L,R);
    }
    a[p].sum=a[a[p].lc].sum+a[a[p].rc].sum;
}

int main(){
    int n,m,q,rtrow,rtcal,id,l,r;
    ll res;
    while(scanf("%d%d%d",&n,&m,&q)!=EOF){
        tot=0;
        rtrow=build();
        rtcal=build();
        while(q--){
            scanf("%d%d%d",&id,&l,&r);
            if(id==1) insert(rtrow,1,n,l,r);
            else insert(rtcal,1,m,l,r);
            res=0;
            if(a[rtrow].sum==0) res=(m-a[rtcal].sum)*1LL*n+a[rtcal].sum;
            else if(a[rtcal].sum==0) res=(n-a[rtrow].sum)*1LL*m+a[rtrow].sum;
            else{
                res=n*1LL*m-a[rtrow].sum*1LL*m-a[rtcal].sum*1LL*n+a[rtrow].sum*1LL*a[rtcal].sum+1;
            }
            printf("%lld\n",res);
        }
    }

    return 0;
}

离散化:线段树节点维护左闭右开区间

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn=1e5+7;

int cc[maxn<<1],rr[maxn<<1];
int nn,mm;
void quchong(int id1,int id2){
    sort(rr+1,rr+1+id1);
    sort(cc+1,cc+1+id2);
    nn=unique(rr+1,rr+1+id1)-rr-1;
    mm=unique(cc+1,cc+1+id2)-cc-1;
}

int getr(int x){ return lower_bound(rr+1,rr+1+nn,x)-rr; }
int getc(int x){ return lower_bound(cc+1,cc+1+mm,x)-cc; }

int row[maxn<<3|1],cal[maxn<<3|1];

bool lr[maxn<<3|1],lc[maxn<<3|1];

void build(int l,int r,int k,int type){
    if(l>r) return ;
    if(type) row[k]=lr[k]=0;
    else cal[k]=lc[k]=0;
    if(l+1==r) return ;
    int mid=(l+r)>>1;
    build(l,mid,k<<1,type);
    build(mid,r,k<<1|1,type);
}

void pushdown(int l,int r,int k,int type){
    if(l+1==r) return ;
    int mid=(l+r)>>1;
    if(type){
        if(lr[k]){
            lr[k<<1]=lr[k<<1|1]=1;
            row[k<<1]=rr[mid]-rr[l];
            row[k<<1|1]=rr[r]-rr[mid];
            lr[k]=0;
        }
    }
    else{
        if(lc[k]){
            lc[k<<1]=lc[k<<1|1]=1;
            cal[k<<1]=cc[mid]-cc[l];
            cal[k<<1|1]=cc[r]-cc[mid];
            lc[k]=0;
        }
    }
}

void updata(int l,int r,int k,int L,int R,int type){
    if(l>r) return ;
    if(l>=L&&r<=R){
        if(type){
            lr[k]=1;
            row[k]=rr[r]-rr[l];
        }
        else{
            lc[k]=1;
            cal[k]=cc[r]-cc[l];
        }
        return ;
    }
    if(l+1==r) return ;
    int mid=(l+r)>>1;
    pushdown(l,r,k,type);
    if(R<=mid) updata(l,mid,k<<1,L,R,type);
    else if(L>=mid) updata(mid,r,k<<1|1,L,R,type);
    else{
        updata(l,mid,k<<1,L,R,type);
        updata(mid,r,k<<1|1,L,R,type);
    }
    if(type) row[k]=row[k<<1]+row[k<<1|1];
    else cal[k]=cal[k<<1]+cal[k<<1|1];
}

int op[maxn],L[maxn],R[maxn];

int main(){
    int n,m,q,id1,id2,l,r;
    while(scanf("%d%d%d",&n,&m,&q)!=EOF){
        id1=0,id2=0;
        for(int i=1;i<=q;++i){
            scanf("%d%d%d",&op[i],&L[i],&R[i]);
            ++R[i];
            if(op[i]==1) rr[++id1]=L[i],rr[++id1]=R[i];
            else cc[++id2]=L[i],cc[++id2]=R[i];
        }
        //rr[++id1]=n+1;
        //cc[++id2]=m+1;
        quchong(id1,id2);
        build(1,nn,1,1);
        build(1,mm,1,0);
        //nn 行  mm 列;
        for(int i=1;i<=q;++i){
            if(op[i]==1){
                l=getr(L[i]),r=getr(R[i]);
                updata(1,nn,1,l,r,1);
            }
            else{
                l=getc(L[i]),r=getc(R[i]);
                updata(1,mm,1,l,r,0);
            }
            int rrr=row[1],ccc=cal[1];
            ll res=0;
            if(!ccc) res=(n-rrr)*1LL*m+rrr;
            else if(!rrr) res=(m-ccc)*1LL*n+ccc;
            else res=n*1LL*m-n*1LL*ccc-m*1LL*rrr+ccc*1LL*rrr+1;
            printf("%lld\n",res);
        }
        build(1,nn,1,1);
        build(1,mm,1,0);

    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值