Luogu P1197 [JSOI2008]星球大戰
阿新 • • 發佈:2020-07-26
思路
這個題表面看好像是一個並查集刪點操作,但是並查集貌似並沒有類似的操作,只有加點和查詢操作。那怎麼辦呢?這時候逆向思維就顯得尤為重要。
我們可以考慮刪點操作轉化為加點操作。把題目中給出的刪點序列逆轉,第一次建成的並查集是執行完所有刪點操作的並查集,然後按照題目中給出順序的逆序執行加點操作,每次統計形成的連通塊個數
(即父親為自己的的點的個數,但是為了讓時間更優秀,我們實際並不是這麼統計的)。
最後把逆序統計出的答案存到數組裡,最後逆序輸出即可。
Code
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #define MAXN 400005 int n, m, k, f[MAXN]; int p[MAXN], h[MAXN];//p是要刪的點的編號,h是標記哪個點要刪 int head[MAXN], cnt;//鏈式前向星存圖 int res[MAXN];//答案 inline int read(void){ int f = 1, x = 0;char ch; do{ch = getchar();if(ch=='-')f = -1;} while (ch < '0' || ch > '9'); do{ x = x * 10 + ch - '0';ch = getchar();} while (ch >= '0' && ch <= '9'); return f * x; } struct node{ int from, nxt, to; } edge[MAXN];//這裡要多加一個from,統計上一個點是誰,下面要用到 inline void add_edge(int x,int y){ ++cnt; edge[cnt].nxt = head[x]; edge[cnt].from = x; edge[cnt].to = y; head[x] = cnt; return; }//鏈式前向星存圖 inline int get_father(int k){ return f[k] == k ? k : f[k] = get_father(f[k]); }//尋根 inline void merge(int x,int y){ f[get_father(x)] = get_father(y); return; }//合併 int main(){ n = read(), m = read(); for (int i = 0; i <= n;++i) f[i] = i;//初始化 for (int i = 1; i <= m;++i){ int u = read(), v = read(); add_edge(u, v), add_edge(v, u);//連雙向邊 } k = read(); for (int i = 1; i <= k;++i) p[i] = read(), h[p[i]] = 1;//把要刪的點標記為 1 int tot = n - k;//刪完點後點的數量 for (int i = 1; i <= cnt; ++i){ if(h[edge[i].from]==0 && h[edge[i].to]==0 && get_father(edge[i].from)!=get_father(edge[i].to)){ --tot, merge(edge[i].from, edge[i].to);//如果該邊上的兩個點都沒有被刪過,且不在同一個並查集裡,合併,聯通塊數量減一 } } res[k + 1] = tot;//存下刪完所有點之後的聯通塊數量 for (int i = k; i >= 1;--i){ ++tot, h[p[i]] = 0;//加上一個點 for (int j = head[p[i]]; j;j=edge[j].nxt){//遍歷所有與它相連的邊 if(h[edge[j].to]==0 && f[get_father(p[i])]!=f[get_father(edge[j].to)]){ --tot, merge(edge[j].to, p[i]);//若該邊連到的點當前沒有被刪除,且這兩個點不在同一聯通塊中,合併,聯通塊數量減一 } } res[i] = tot;//統計答案 } for (int i = 1; i <= k+1;++i) printf("%d\n", res[i]);//最後輸出不要忘了還有初始狀態 return 0; }