[USACO18OPEN] Multiplayer Moo (并查集+维护并查集技巧)

3 篇文章 0 订阅
2 篇文章 0 订阅

题目大意:给你一个N*N的棋盘,棋盘上每个点都有一个权值

第一问求一个权值形成的最大联通块中点的数量 

 第一问求两个权值共同形成的最大联通块中点的数量 

提供一种并查集的做法:(感谢大佬们的题解)
第一问把所有相同权值的相邻的点用带权并查集合并一下就OK了

第二问,就需要一些骚操作了

我们的目的是把两个不同权值的所有联通块合并,再去看它们共同形成的最大联通块的大小

可以用一个结构体记录两个联通块之间的关系

分别是两个联通块的标号(即这个联通块构成的并查集的祖先节点)

以及这两个联通块的颜色

而为了简化后面的匹配过程,要把第一个块的颜色编号改成较小的,第二个块的颜色编号改成较大的

然后对这个结构体按颜色编号的首项排序,如果首项相同就按第二项排序

这么做的目的是,让 两个相同颜色的联通块之间的关系 形成一段连续的区间,这样我们就可以省去很多时间!!!

然后每次都按关系从前到后 去合并两个颜色,统计答案,再用几个数组把并查集还原回去就行了;

#include <cstdio>
#include <algorithm>
#include <cstring>
#define N 255
#define maxn 1000010
using namespace std;

int n,cnt;
int a[N][N],fa[N*N],id[N][N],use[N*N],que[N*N],sum[N*N],sm[N*N];
struct E{
    int x,y,c1,c2;
}e[N*N*4];
int cmp(E s1,E s2)
{
    if(s1.c1!=s2.c1) return s1.c1<s2.c1;
    else return s1.c2<s2.c2;
}

void e_add(int xx,int yy,int cc1,int cc2)
{
    cnt++;
    if(cc1>cc2) swap(cc1,cc2),swap(xx,yy);
    e[cnt].x=xx,e[cnt].c1=cc1;
    e[cnt].y=yy,e[cnt].c2=cc2;
}
bool check(int x,int y)
{
    if(x<1||y<1||x>n||y>n) return false;
    else return true;
}
int find_fa(int x)
{
    int fx=x;
    while(fx!=fa[fx]) fx=fa[fx];
    while(x!=fx){
        int pre=fa[x];
        fa[x]=fx;
        x=pre;
    } 
    return x;
}
void mrg1(int x,int y)
{
    x=find_fa(x),y=find_fa(y);
    if(x!=y){
        fa[y]=x;
        sum[x]+=sum[y];
    }
}
void mrg2(int x,int y)
{
    x=find_fa(x),y=find_fa(y);
    if(x!=y){
        fa[y]=x;
        sm[x]+=sm[y];
    }
} 

int main()
{   
    //freopen("data.in","r",stdin);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            scanf("%d",&a[i][j]);
            id[i][j]=(i-1)*n+j;
        }
    for(int i=1;i<=n*n;i++) sum[i]=1,fa[i]=i;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            if(check(i+1,j)&&a[i][j]==a[i+1][j])
                mrg1(id[i][j],id[i+1][j]);
            if(check(i,j+1)&&a[i][j]==a[i][j+1])
                mrg1(id[i][j],id[i][j+1]);
        }
    for(int i=1;i<=n*n;i++) sm[i]=sum[i];
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            if(check(i+1,j)&&a[i][j]!=a[i+1][j])
                e_add(find_fa(id[i][j]),find_fa(id[i+1][j]),a[i][j],a[i+1][j]);
            if(check(i,j+1)&&a[i][j]!=a[i][j+1])
                e_add(find_fa(id[i][j]),find_fa(id[i][j+1]),a[i][j],a[i][j+1]);
        }
    sort(e+1,e+cnt+1,cmp);
    int ct=0,ret=0;
    for(int i=1;i<=n*n;i++)
        ret=max(ret,sum[i]);
    printf("%d\n",ret);
    ret=0;
    for(int i=1;i<=cnt;i++,ct=0)
    { 
        que[++ct]=e[i].x,que[++ct]=e[i].y;
        use[e[i].x]=use[e[i].y]=1;
        mrg2(e[i].x,e[i].y);  //merge
        while(e[i+1].c1==e[i].c1&&e[i+1].c2==e[i].c2){
            i++;
            if(!use[e[i].x]) use[e[i].x]=1,que[++ct]=e[i].x;
            if(!use[e[i].y]) use[e[i].y]=1,que[++ct]=e[i].y;
            mrg2(e[i].x,e[i].y);
        }
        for(int j=1;j<=ct;j++) //calc
            ret=max(ret,sm[que[j]]);
        for(int j=1;j<=ct;j++) //clear
        {
            fa[que[j]]=que[j];
            sm[que[j]]=sum[que[j]];
            use[que[j]]=0;
        }
    }
    printf("%d\n",ret);
    return 0;
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值