NBUT 1508 火烧赤壁2【离线+逆序并查集】

转载请注明出处:http://blog.csdn.net/a1dark

分析:由于这题N、M、T数据都非常大、于是我们要想办法优化一下、首先要离线、然后逆序查询、这样就不用每次重新构图、只需要每次把当前点的相关边、并入集合中、看连通块有多少、由于内存的限制、所以需要选择合理的存储结构、于是邻接表是必然的、具体见代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int maxn=200005;//题目要求数据
int mpt[maxn];//用于并查集
int num[maxn];//用于离线记录数据
int ans[maxn];//用于保存答案
int vis[maxn];//用于标记是否在集合中
int n,m,temp;//N个点、M条边、temp表示答案的变量
struct node{
    int to,next;//用于临界表存储
}map[maxn*2];
int head[maxn];
int k;//临界表索引
void add(int x,int y){//邻接表添加边
    k++;
    map[k].to=y;
    map[k].next=head[x];
    head[x]=k;
}
void init(){//初始化并查集
    for(int i=0;i<n;i++){
        mpt[i]=i;
    }
}
int find(int x){//寻找祖先并压缩路劲
    int r=x;
    while(r!=mpt[r]){
        r=mpt[r];
    }
    int b=x;
    int f;
    while(b!=r){
        f=mpt[b];
        mpt[b]=r;
        b=f;
    }
    return r;
}
void merge(int x,int y){//合并两个点
    int fx=find(x);
    int fy=find(y);
    if(fx!=fy){
        mpt[fx]=fy;
        temp--;//若祖先不同则合并后少一个连通块
    }
}
int main(){
    while(scanf("%d%d",&n,&m)!=EOF){
        memset(vis,0,sizeof(vis));
        memset(num,0,sizeof(num));
        init();
        temp=n;
        k=-1;//初始化邻接表
        for(int i=0;i<maxn;i++)
                head[i]=-1;
        int a,b;
        for(int i=0;i<m;i++){
            scanf("%d%d",&a,&b);
            add(a,b);//加入邻接表
            add(b,a);
        }
        int test;
        scanf("%d",&test);
        for(int i=0;i<n;i++)//初始化标记数组
            vis[i]=0;
        for(int i=1;i<=test;i++){
            scanf("%d",&num[i]);
            vis[num[i]]=1;
        }
        init();//初始化并查集
        temp=n-test;
        for(int i=0;i<n;i++){
            if(vis[i]==0){
                int now=i;
                for(int j=head[now];j!=-1;j=map[j].next){//重新构图
                    int s=map[j].to;
                    if(vis[s]==0){
                        merge(now,s);
                        vis[now]=0;
                    }
                }
            }
        }
        ans[test+1]=temp;//讲答案逆序保存进ans数组中
        temp++;//每一次多一个点则+1
        for(int i=test;i>=1;i--){//循环test次
            int now=num[i];
            vis[now]=0;
            for(int j=head[now];j!=-1;j=map[j].next){//找相邻的边
                int s=map[j].to;
                if(vis[s]==0){//若相邻的边在图上、则合并
                    merge(now,s);
                    vis[now]=0;
                }
            }
            ans[i]=temp;//记录当前答案
            temp++;
        }
        for(int i=1;i<=test+1;i++)//输出答案
            printf("%d\n",ans[i]);
    }
    return 0;
}


你可能感兴趣的:(并查集)