1. 程式人生 > 實用技巧 >Luogu P1197 [JSOI2008]星球大戰

Luogu P1197 [JSOI2008]星球大戰

思路

這個題表面看好像是一個並查集刪點操作,但是並查集貌似並沒有類似的操作,只有加點和查詢操作。那怎麼辦呢?這時候逆向思維就顯得尤為重要。

我們可以考慮刪點操作轉化為加點操作。把題目中給出的刪點序列逆轉,第一次建成的並查集是執行完所有刪點操作的並查集,然後按照題目中給出順序的逆序執行加點操作,每次統計形成的連通塊個數

(即父親為自己的的點的個數,但是為了讓時間更優秀,我們實際並不是這麼統計的)。

最後把逆序統計出的答案存到數組裡,最後逆序輸出即可。

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;
}