题解 | #[JSOI2008]星球大战STARWAR#

[JSOI2008]星球大战STARWAR

https://ac.nowcoder.com/acm/problem/20163

并查集 + 邻接表 + 逆向思维

逆向思维:

原题意:共要删除 k 个点,问:每次删除过后,连通块个数是多少?
逆向思维:删点 ==> 加点、连边   对!没错,变成了加点与连边

所以最开始,我们只对 n-k 个点进行合并操作就行,然后枚举每个要删除的点(注意是从后向前枚举)
对于每个枚举到的 "要删点",我们执行 加点 与 连边操作(合并操作)
加点:加上该点
连边:对 该点 和 与其直接相连的点们 进行并查集的合并操作

ps:被删点 和 被删点 可能直接相连,比如 题目让我们先删 6,再删 3,但 3、6 直接相连
当我们枚举时,因为是逆着枚举,所以我们会先枚举到 3 再枚举到 6

当枚举到 3 时:3 和 6 直接相连,但此时我们不应对他们进行合并操作
因为这个时候图上还没有点6,6 还没有被我们加上去呢!!6 会在接下来也就是枚举到它时才被加上去!

当我们枚举到 6 时:3 和 6 直接相连,此时我们可以对他们进行合并操作了,因为 3、6 此时都已在图上啦

邻接表

一条"链子"叫 单链表,很多条"链子"就组成了邻接表,我们使用邻接表来存储与每个被删点直接相连的点
为什么用邻接表???当然是因为开二维数组开不下啊!!!

~~没学过邻接表的,可以学一下,图论题离不开邻接表~~

代码中 一些变量或语句 的解释

D[i]:指第 i 个被删的点
st[i]:i 这个点是第几个被删除
ans[i]:第 i 个点被删除后,目前的连通块块数

x、y 都是被删点的时候为什么要判断 st[x]、st[y] 谁大?
这个 就是我们在 逆向思维Part 所提到的了,x、y 都是被删点 且 两者直接相连的时候
为了避免 枚举到一个点的时候,另一个点还没有被加到图上,我们需要考虑 它们两个被删的先后顺序

语句:res减1 执行时的场合 ==> 合并两个不相连的连通块时

Code

#include <bits/stdc++.h>

using namespace std;

typedef pair<int,int> PII;

const int N = 1e6+10;

PII p[N];
int h[N],e[N],ne[N],idx;  // 邻接表
int st[N],fa[N],D[N],ans[N];
int n,m,k;

void add(int a,int b){  // 添加 a 指向 b 的边
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

int find(int x){
    if(x==fa[x]) return x;
    return fa[x]=find(fa[x]);
} 

void Init(){
     memset(h,-1,sizeof h);   // 邻接表初始化
     for(int i=0;i<n;i++) fa[i]=i;  // 并查集初始化,点是0~n-1(注意题目)
}

void Input(){
    cin>>n>>m;
    for(int i=0;i<m;i++)  cin>>p[i].first>>p[i].second;
    cin>>k;
    for(int i=1;i<=k;i++)  cin>>D[i],st[D[i]]=i;
}

void solve(){
    int res=n-k; //逆向思维,删除k个点后,剩n-k个
    for(int i=0;i<m;i++){
        int x=p[i].first,y=p[i].second;
        if(!st[x]&&!st[y]){   // 对剩余的n-k个点先进行合并操作
            x=find(x),y=find(y);
            if(x!=y) fa[x]=y,res--; 
        }
        else if(!st[x]&&st[y])  add(y,x);   // 说明y是被删点,通过邻接表存储和y相连的点
        else if(st[x]&&!st[y])  add(x,y);  // 说明x是被删点,...
        else { // 说明x、y都是被删点,...
            if(st[x]>st[y]) add(y,x);  
            else add(x,y);
        }
    } 

    for(int i=k;i>=1;i--){
        ans[i]=res;
        int id=D[i];
        res++;   //记得先加上1:加点操作
        for(int ii=h[id];ii!=-1;ii=ne[ii]){  //合并与D[i]直接相连的点
            int j=e[ii];
            id=find(id),j=find(j);
            if(id!=j){
                res--;
                fa[id]=j;
            }
        }
    }
    cout<<res<<endl;
    for(int i=1;i<=k;i++) cout<<ans[i]<<endl;
}

int main(){
    Input();   Init();   solve();
    return 0;
} 
全部评论

相关推荐

美团 后端开发 总包n(15%是股票)
点赞 评论 收藏
分享
字节 飞书绩效团队 (n+2) * 15 + 1k * 12 + 1w
点赞 评论 收藏
分享
3 收藏 评论
分享
牛客网
牛客企业服务