P1197 [JSOI2008]星球大战(并查集+逆向)

题目描述

很久以前,在一个遥远的星系,一个黑暗的帝国靠着它的超级武器统治着整个星系。

某一天,凭着一个偶然的机遇,一支反抗军摧毁了帝国的超级武器,并攻下了星系中几乎所有的星球。这些星球通过特殊的以太隧道互相直接或间接地连接。

但好景不长,很快帝国又重新造出了他的超级武器。凭借这超级武器的力量,帝国开始有计划地摧毁反抗军占领的星球。由于星球的不断被摧毁,两个星球之间的通讯通道也开始不可靠起来。

现在,反抗军首领交给你一个任务:给出原来两个星球之间的以太隧道连通情况以及帝国打击的星球顺序,以尽量快的速度求出每一次打击之后反抗军占据的星球的连通块的个数。(如果两个星球可以通过现存的以太通道直接或间接地连通,则这两个星球在同一个连通块中)。

输入输出格式

输入格式:

 

输入文件第一行包含两个整数,NN (1 < = N < = 2M1<=N<=2M) 和 MM (1 < = M < = 200,0001<=M<=200,000),分别表示星球的数目和以太隧道的数目。星球用 00 ~ N-1N−1 的整数编号。

接下来的 MM 行,每行包括两个整数 XX, YY,其中( 0 < = X <> Y0<=X<>Y 表示星球 xx 和星球 yy 之间有 “以太” 隧道,可以直接通讯。

接下来的一行为一个整数 kk ,表示将遭受攻击的星球的数目。

接下来的 kk 行,每行有一个整数,按照顺序列出了帝国军的攻击目标。这 kk 个数互不相同,且都在 00 到 n-1n−1 的范围内。

 

输出格式:

 

第一行是开始时星球的连通块个数。接下来的 KK 行,每行一个整数,表示经过该次打击后现存星球的连通块个数。

思路:由于将点一个一个从并查集中拿去太难,所以逆向思考,将点一个一个加入并查集,倒序输出。

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define mem(ar,num) memset(ar,num,sizeof(ar))
#define me(ar) memset(ar,0,sizeof(ar))
#define debug cout<<endl<<"DEBUG"<<endl;
#define qx std::ios::sync_with_stdio(false)
using namespace std;
int d[400010],n,m,a,b,arr[400010],ans[400000],cnt,po[400000],k;
int f(int x){return d[x] == x ? x : d[x] = f(d[x]);}
vector<int> v[400010];
int main(){qx;
    cin>>n>>m;
    for(int i=1;i<n;i++)d[i]=i;
    for(int i=0;i<m;i++){
        cin>>a>>b;
        v[a].push_back(b);
        v[b].push_back(a);
    }
    cin>>k;
    for(int i=0;i<k;i++){
        cin>>po[i];
        arr[po[i]]=1;
    }
    for(int i=0;i<n;i++){
        if(arr[i])continue;
        for(int j=0;j<v[i].size();j++){
            b=v[i][j];
            if(arr[b])continue;
            int x=f(i),y=f(b);
            if(x!=y)
                d[x]=y;
        }
    }
    for(int i=0;i<n;i++){
        if(d[i]==i&&!arr[i])ans[cnt]++;
    }
    int num=ans[cnt];
    cnt++;
    for(int i=k-1;i>=0;i--){
        arr[po[i]]=0;//刚刚修建了一个新的星球
        num++;//多了一个点,所以联通块+1
        for(int j=0;j<v[po[i]].size();j++){
            if(arr[v[po[i]][j]]==0){//如果这里也修建好了
                b=v[po[i]][j];
                int x=f(po[i]),y=f(b);
                if(x!=y){
                    d[x]=y;
                    num--;//合并为一个联通块
                }
            }
        }
        ans[cnt]=num;
        cnt++;
    }
    for(int i=cnt-1;i>=0;i--){
        cout<<ans[i]<<endl;
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值