1. 程式人生 > >洛谷P1197 【JSOI2008】星球大戰

洛谷P1197 【JSOI2008】星球大戰

題目傳送門

這道題大部分的人一開始的思路都是一個一個的刪除點再求聯通塊數,但問題是斷開一條邊並不意味著會有一個聯通塊斷成兩個,所以我們只能遍歷整個圖,而這個做法的時間複雜度是我們接受不了的。

既然刪點行不通,我們不妨逆向思考,從最後的狀態開始加點,這樣只需要用並查集維護聯通,然後每次遍歷加入的點的連邊就行了。如果新加入的點有將兩個原來不相連的聯通塊連線起來的邊,那麼聯通塊的數量就會減少。

上程式碼

#include<iostream>
#include<cstdio>
using namespace std;
const int N=200010;
int
h[N<<1],to[N<<1],pre[N<<1];//鄰接表 int n,m,q,top,fa[N<<1],d[N<<1],ans[N<<1]; //fa記錄並查集,d按順序記錄被摧毀的星球編號,ans[i]記錄摧毀了i個星球后聯通塊數量 bool vis[N<<1];//記錄當前時刻每個星球是否被摧毀,為true則為被摧毀 int read()//快讀 { int sum=0;char ch=getchar(); while(ch<'0'||ch>'9')ch=getchar(); while(ch>=
'0'&&ch<='9')sum=(sum<<3)+(sum<<1)+ch-'0',ch=getchar(); return sum; } inline void ins(int u,int v)//插入邊 { pre[++top]=h[u];h[u]=top;to[top]=v; } int sfind(int x)//詢問並查集 { if(x!=fa[x])fa[x]=sfind(fa[x]); return fa[x]; } int main() { n=read();m=read(); for(int i=0;i<n;i++)fa[
i]=i;//初始化並查集 for(int i=1;i<=m;i++) { int x=read(),y=read();ins(x,y);ins(y,x); } q=read();ans[q]=n-q;//在摧毀q顆星球后最多會有(n-q)個聯通塊 for(int i=1;i<=q;i++) { int x=read();vis[x]=true;d[i]=x; }//記錄被摧毀的星球 for(int i=0;i<n;i++)//找到最後時刻的聯通塊數量 { if(vis[i])continue;//如果當前點已經被摧毀,那麼直接跳過 int fx=sfind(i); for(int j=h[i];j;j=pre[j]) { if(vis[to[j]])continue;//如果當前邊連的點被摧毀就跳過 int fy=sfind(to[j]); if(fx!=fy)fa[fy]=fx,ans[q]--;//這條邊溝通了兩個聯通塊,聯通塊數量減一 } } for(int i=q;i>0;i--)//倒序計算每個星球被摧毀後的聯通塊數量 { vis[d[i]]=false;int fx=sfind(d[i]);//先恢復當前點 ans[i-1]=ans[i]+1;//圖裡加入了一個單獨的點,聯通塊數量加一 for(int j=h[d[i]];j;j=pre[j]) { if(vis[to[j]])continue; int fy=sfind(to[j]); if(fx!=fy)fa[fy]=fx,ans[i-1]--;//連線了兩個聯通塊,答案減一 } } for(int i=0;i<=q;i++)printf("%d\n",ans[i]); return 0; }