Codeforces 811 E. Vladik and Entertaining Flags (线段树+并查集)

题目描述

传送门

题目大意:用公共边且颜色相同的两个块属于同一个连通块,每次询问一个区间中有多少个连通块块。

题解

线段树+并查集
比较基本的思路就是因为行数比较小所以可以对于每个区间的左右两列维护并查集。
维护的东西必须保证在该区间中可以连通的两个位置的编号相同。
两个区间合并的时候,将两个区间左右两列的编号在并查集中的父亲赋值成自己。如果两个区间相邻的位置颜色相同就用并查集合并起来,然后此时再从并查集中查询区间左右列的编号即可。
也就是说我们每个区间的左右两列维护的是一个连通块的编号,合并的时候用并查集进行合并。
具体过程见代码吧。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#define N 100003
using namespace std;
int L[N*4][12],R[N*4][12],sum[N*4],cnt,q;
int fa[N*10],a[12][N],n,m,ls[13],rs[13],ans;
bool pd=false;
int find(int x)
{
    if (fa[x]==x) return fa[x];
    fa[x]=find(fa[x]);
    return fa[x];
}
void update(int now,int l,int r)
{
    int mid=(l+r)/2;
    int ls=now<<1; int rs=now<<1|1;
    sum[now]=sum[ls]+sum[rs];
    for (int i=1;i<=n;i++) {
        fa[L[ls][i]]=L[ls][i];
        fa[L[rs][i]]=L[rs][i];
        fa[R[ls][i]]=R[ls][i];
        fa[R[rs][i]]=R[rs][i];
    }
    for (int i=1;i<=n;i++)
     if (a[i][mid]==a[i][mid+1]) {
        int r1=find(L[rs][i]); int r2=find(R[ls][i]);
        if (r1!=r2) {
            sum[now]--;
            fa[r2]=r1;
         }
     }
    for (int i=1;i<=n;i++) {
        L[now][i]=find(L[ls][i]);
        R[now][i]=find(R[rs][i]);
    }
}
void build(int now,int l,int r)
{
    if (l==r) {
        for (int i=1;i<=n;i++)
         if (a[i][l]==a[i-1][l]) L[now][i]=R[now][i]=L[now][i-1];
         else L[now][i]=R[now][i]=++cnt,sum[now]++;
        return;
    }
    int mid=(l+r)/2;
    build(now<<1,l,mid);
    build(now<<1|1,mid+1,r);
    update(now,l,r);
}
void query(int now,int l,int r,int ll,int rr)
{
    if (ll<=l&&r<=rr) {
        if (!pd) {
            pd=true;
            ans=sum[now];
            for (int i=1;i<=n;i++) ls[i]=L[now][i],rs[i]=R[now][i];
        }
        else {
            ans+=sum[now];
            for (int i=1;i<=n;i++) {
                fa[ls[i]]=ls[i];
                fa[L[now][i]]=L[now][i];
                fa[rs[i]]=rs[i];
                fa[R[now][i]]=R[now][i];
            }   
            for (int i=1;i<=n;i++) 
             if (a[i][l]==a[i][l-1]) {
                int r1=find(L[now][i]); int r2=find(rs[i]);
                if (r1!=r2) {
                    ans--;
                    fa[r2]=r1;
                 }
             }
            for (int i=1;i<=n;i++) {
                ls[i]=find(ls[i]);
                rs[i]=find(R[now][i]);
            }
        }
        return;
    }
    int mid=(l+r)/2;
    if (ll<=mid) query(now<<1,l,mid,ll,rr);
    if (rr>mid) query(now<<1|1,mid+1,r,ll,rr);
}
int main()
{
    freopen("a.in","r",stdin);
    scanf("%d%d%d",&n,&m,&q);
    for (int i=1;i<=n;i++) 
     for (int j=1;j<=m;j++) scanf("%d",&a[i][j]);
    build(1,1,m);
    for (int i=1;i<=q;i++) {
        int l,r; scanf("%d%d",&l,&r);
        pd=false; ans=0;
        query(1,1,m,l,r);
        printf("%d\n",ans);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值