洛谷1197并查集+离线+删点+判连通分量

题目链接:https://www.luogu.com.cn/problem/P1197

题目给出一些点之间边,然后要摧毁某些结点,问每一次摧毁之后的图的连通分量数量。我们考虑到判连通分量的数量只要扫一遍并查集的根节点就可以,所以考虑用这种数据结构来检查连通分量的数量。其次,并查集中删除结点的消耗是非常大的,每次删除结点需要扫描所有的点才能知道连通分量的数量。但是添加结点的效率是非常高的,所以我们考虑倒过来操作,先把将所有没有摧毁了的点的边连上,然后每次都添加一个最后删除的点,检查一遍连通分量(只要结合一次连通分量必然减少一个),最终把所有的点都连上便是初始状态。

代码如下:

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 typedef unsigned int ui;
  4 typedef long long ll;
  5 typedef unsigned long long ull;
  6 #define pf printf
  7 #define mem(a,b) memset(a,b,sizeof(a))
  8 #define prime1 1e9+7
  9 #define prime2 1e9+9
 10 #define pi 3.14159265
 11 #define lson l,mid,rt<<1
 12 #define rson mid+1,r,rt<<1|1
 13 #define scand(x) scanf("%llf",&x) 
 14 #define f(i,a,b) for(int i=a;i<=b;i++)
 15 #define scan(a) scanf("%d",&a)
 16 #define mp(a,b) make_pair((a),(b))
 17 #define P pair<int,int>
 18 #define dbg(args) cout<<#args<<":"<<args<<endl;
 19 #define inf 0x3f3f3f3f
 20 const int maxn=1e6+10;
 21 int n,m,t;
 22 inline int read(){
 23     int ans=0,w=1;
 24     char ch=getchar();
 25     while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
 26     while(isdigit(ch))ans=(ans<<3)+(ans<<1)+ch-'0',ch=getchar();
 27     return ans*w;
 28 }
 29 int f[maxn],vis[maxn],id[maxn],head[maxn],nxt[maxn],num[maxn];
 30 int e;
 31 struct node{
 32     int u,v;
 33 }p[maxn];
 34 void init()
 35 {
 36     f(i,0,n-1)f[i]=i;
 37     mem(vis,0);
 38     e=0;
 39     mem(num,0);
 40     mem(head,-1);
 41     mem(nxt,-1);
 42  } 
 43 int find(int x)
 44 {
 45     if(x==f[x])return x;
 46     return f[x]=find(f[x]);
 47 }
 48 void addedge(int u,int v)
 49 {
 50     p[e].v=v;
 51     p[e].u=u;
 52     nxt[e]=head[u];
 53     head[u]=e++;
 54 }
 55 int main()
 56 {
 57     //freopen("input.txt","r",stdin);
 58     //freopen("output.txt","w",stdout);
 59     std::ios::sync_with_stdio(false);
 60     n=read(),m=read();
 61     init();
 62     int u,v;
 63     f(i,1,m)
 64     {
 65         u=read(),v=read();
 66         addedge(u,v);//建边不用管权值,只需要知道两个点是否相连,并且并查集中的边是双向连通的
 67         addedge(v,u); 
 68     }
 69     t=read();
 70     f(i,1,t)id[i]=read(),vis[id[i]]=1;
 71     int cnt=n-t;//最初有n-k个点,也就是n-k个连通分量,是k次打击之后剩下来的点 
 72     f(i,0,n-1)
 73     {
 74         for(int j=head[i];~j;j=nxt[j])
 75         {
 76             if(!vis[p[j].u]&&!vis[p[j].v])//枚举每个点的每条边,开始建立摧毁完结点之后的图 
 77             {
 78                 int x=find(p[j].u);
 79                 int y=find(p[j].v);
 80                 if(x==y)continue;
 81                 else cnt--,f[x]=y;//每次合并两个连通分量之后分量数量都减一 
 82             }
 83         }
 84     }
 85     num[t]=cnt;
 86     for(int i=t;i>=1;i--)
 87     {
 88         int s=id[i];
 89         cnt++;//修复一个点之后连通分量加一
 90         vis[s]=0;//加点之后注意标记这个点是未被打击的,图中只保持未被打击的点之间有连线 
 91         for(int j=head[s];~j;j=nxt[j])
 92         {
 93             if(!vis[p[j].v])
 94             {
 95                 int x=find(p[j].u);
 96                 int y=find(p[j].v);
 97                 if(x==y)continue;
 98                 else cnt--,f[x]=y;    
 99             }
100         }
101         num[i-1]=cnt;
102     }
103     f(i,0,t)pf("%d
",num[i]);
104  } 
原文地址:https://www.cnblogs.com/randy-lo/p/12566607.html