1. 程式人生 > >【follow】BZOJ1015: [JSOI2008]星球大戰starwar

【follow】BZOJ1015: [JSOI2008]星球大戰starwar

意思是給一個圖,每次刪除一個點和與其相關的連邊,每次求出連通塊的個數。

這道題按照題目的說法,很不好想。但是如果把題目倒過來,我們只需要每次在圖中新增節點,並判斷新增的節點是否:{產生新的連通塊,將兩個已有連通塊連在一起}即可。

這就是並查集的思想。

首先我們建一張參照圖,建立題中的所有邊。

用ans表示現在的連通塊個數,初始為0即可。

然後統計出那些點沒有被踢出(用ove來表示),建立一張新的圖。統計這個圖的聯通塊數量,存為ans,存入最終輸出的答案陣列中。

逆序新增點,每次新增,更新ans,存入最後輸出的答案陣列中。

最後逆序輸出答案陣列即可。

[更新操作]insert

首先,此點產生了一個新的連通塊,ans++.    (*)

我們加入了一個點。首先要按照原圖,尋找相連的邊,如果此邊通向的點在圖中,並且與加入的點不在一個連通塊(用並查集即可),那麼ans--,並連線這兩個連通塊。

不在一個連通塊的話就不用管了。(因為已經ans++了。(*處))

#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=400010;
int fa[maxn],head[maxn],cnt,d[maxn],gnt;
bool ove[maxn],use[maxn];
int n,m,k,sum[maxn],ans;
struct edge
{
  int nxt,to;
}e[maxn],g[maxn];
void add1(int x,int y)//對照組(所有邊) 
{
  e[++cnt].to=y;
  e[cnt].nxt=head[x];
  head[x]=cnt;
}
void add2(int x,int y)//正式組(被攻打之後) 
{
  g[++gnt].to=y;
  g[gnt].nxt=head[x];
  head[x]=gnt;
}
int find(int x)
{
  if(fa[x]==x)return x;
  return fa[x]=find(fa[x]);
}
void insert(int x)
{
  for(int i=head[x];i;i=e[i].nxt)
   if(use[e[i].to]==1)//在圖中 
    {
      add2(x,e[i].to);
	  if(find(e[i].to)!=find(x))
	   {
	     ans--;
	     fa[find(x)]=find(e[i].to);
	   }
    }
}
int main()
{
  cin>>n>>m;
  for(int i=1;i<=n;i++)fa[i]=i;
  int x,y;
  for(int i=1;i<=m;i++)
   {
   	 scanf("%d%d",&x,&y);
   	 x++,y++;
   	 add1(x,y);
   	 add1(y,x);
   }
   cin>>k;
  for(int i=1;i<=k;i++)
   {
     scanf("%d",&d[i]);
     d[i]++;
     ove[d[i]]=1;
   }
  for(int i=1;i<=n;i++)
  if(ove[i]==0)
   {
     insert(i);
     ans++;
     use[i]=1;
   }
   sum[k+1]=ans;
   for(int i=k;i>=1;i--)
   {
   	 insert(d[i]);
   	 ans++;
   	 use[d[i]]=1;
   	 sum[i]=ans;
   }
  for(int i=1;i<=k+1;i++)printf("%d\n",sum[i]);
 return 0;
}