沈阳化工大学第十一届程序设计沈阳区竞赛:关键学生(并查集,思维)

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
 

题目描述

“--------------拉帮结伙------------------”
    某星光小学近期发现许多学习喜欢成为类似江湖上的帮派,现在老师对这个问题开始着手解决了。。
    已知某个班级有nnn个学生,现在给出mmm对关系,如果某两个学生直接或者间接存在关系,我们便认为他们处于同一个帮派中(自己也是一个帮派)。老师们为了防止帮派太过团结, 将进行kkk次抓人,每抓走一个人,所以与这个人有直接或者间接的关系都将消失。
    现在向你询问,每次老师抓走一个人之后,班级当前存在多少个帮派?

输入描述:

第一行两个正整数n,m​(1≤n≤104,1≤m≤2×105)n, m​(1 \leq n \leq 10^4, 1 \leq m \leq 2 \times 10^5)n,m​(1≤n≤104,1≤m≤2×105), 分别表示某个班级里学生的个数和学生直接关系的数量。
首先输入nnn个学生的姓名。
接下来mmm行,每行两个字符串s1,s2(∣s1∣,∣s2∣≤10)s1, s2(|s1|, |s2| \leq 10)s1,s2(∣s1∣,∣s2∣≤10),表示两名具有直接关系的学生姓名。
接下来输入一个正整数k(1≤k≤n)k(1 \leq k \leq n)k(1≤k≤n), 表示老师抓走学生的次数。
随后kkk行,每行一个字符串sss, 表示老师本次抓走的学生姓名。
数据保证nnn个学生姓名不同,同时抓走的kkk个学生姓名不同,且都是本班级同学姓名。

输出描述:

第一行输出没有抓人之前,班级的帮派数量。
接下来kkk行,每行输出抓走当前这个学生之后,班级的帮派数量。

示例1

输入

复制6 10 Lily Maximilian Ava Christopher Zoey Ethan Lily Maximilian Ava Christopher Zoey Ethan Maximilian Ava Ava Zoey Maximilian Ethan Lily Ava Lily Christopher Ethan Lily Christopher Ethan 4 Zoey Maximilian Lily Christopher

6 10
Lily Maximilian Ava Christopher Zoey Ethan
Lily Maximilian
Ava Christopher
Zoey Ethan
Maximilian Ava
Ava Zoey
Maximilian Ethan
Lily Ava
Lily Christopher
Ethan Lily
Christopher Ethan
4
Zoey
Maximilian
Lily
Christopher

输出

复制1 1 1 1 2

1
1
1
1
2

示例2

输入

复制8 13 a b c d e f g h a b b g g f f a a g b c c d d e e f h b h c h g d g 5 b g d f h

8 13
a b c d e f g h
a b
b g
g f
f a
a g
b c
c d
d e
e f
h b
h c
h g
d g
5
b
g
d
f
h

输出

复制1 1 1 2 3 3

1
1
1
2
3
3

做法

本来想用并查集写,但是在不知道怎么实现,就用bfs暴力了,结果超时了。

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10,M=2e5+10;
int n,m,k,ans;
unordered_map<string,int> id;
unordered_map<int,string> name;
vector<int> g[N];
int vis[N],dis[N],sc[N];
queue<int> q;
int sign;
void bfs(int x){
    q.push(x);
    while(q.size()){
        int tmp=q.front();
        q.pop();
        if(dis[tmp]||sc[tmp]) continue;
        dis[tmp]=1;
        for(auto u:g[tmp]){
            if(dis[u]||vis[u]||sc[u]) continue;
            vis[u]=1;
            q.push(u);
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        string s;
        cin>>s;
        id[s]=i;
        name[i]=s;
    }
    for(int i=1;i<=m;i++){
        string a,b;
        cin>>a>>b;
        g[id[a]].push_back(id[b]);
        g[id[b]].push_back(id[a]);
    }
    scanf("%d",&k);
    for(int i=1;i<=n;i++){
        if(vis[i]) continue;
        bfs(i);
        ans++;
    }
    cout<<ans<<endl;
    while(k--){
        string s;
        ans=0;
        cin>>s;
        sc[id[s]]=1;
        for(int i=1;i<=n;i++) vis[i]=0,dis[i]=0;
        for(int i=1;i<=n;i++){
            if(vis[i]||sc[i]) continue;
            bfs(i);
            ans++;
        }
        cout<<ans<<endl;
    }
}

正解

实际上还是用并查集,只不过我们对删除操作倒着实现,把要删掉的人不做合并,算出集合个数。然后再一个个合并要删掉的人。

但是吧,实现过程有出了点错,导致又超时了。具体如下。

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10,M=2e5+10;

int n,m,k;
unordered_map<string,int> id;//名字对应的序号
unordered_map<int,string> name;//序号对应的名字
vector<int> g[N];//每对关系
vector<int>ans,sc;//记录答案和删除的人
int fa[N];
unordered_map<int,int> mp;//记录是否是要删掉的人

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

void setfa(int x,int y){
    fa[getfa(x)]=getfa(y);
}

int main(){
	
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        string s;
        cin>>s;
        id[s]=i;
        name[i]=s;
        fa[i]=i;
    }
    
    for(int i=1;i<=m;i++){
        string a,b;
        cin>>a>>b;
        g[id[a]].push_back(id[b]);
        g[id[b]].push_back(id[a]);
    }
    
    scanf("%d",&k);
    for(int i=1;i<=k;i++){
        string s;
        cin>>s;
        sc.push_back(id[s]);
        mp[id[s]]=1;
    }
    
    //先算出没有被删除的人的集合数
    for(int i=1;i<=n;i++){
        if(mp[i]) continue;
        for(auto u:g[i]){
            if(mp[u]) continue;
            if(getfa(i)!=getfa(u))
                setfa(i,u);
        }
    }
    
    unordered_map<int,int> e;
    for(int i=1;i<=n;i++){
        if(count(sc.begin(),sc.end(),i)) continue;
        e[getfa(i)]++;
    }
    ans.push_back(e.size());
    int res=e.size();
    
    
    for(int i=k-1;i>=0;i--){
    	
    	
    	set<int> s;
        for(auto u:g[sc[i]]){
            if(count(sc.begin(),sc.end(),u)) continue;
            s.insert(getfa(u));
            if(getfa(u)!=getfa(sc[i]))
                setfa(sc[i],u);
        }
        sc.pop_back();
        res-=s.size()-1;
        ans.push_back(res);
    	
//        for(auto u:g[sc[i]]){
//        	if(count(sc.begin(),sc.end(),u)) continue;
//            if(getfa(u)!=getfa(sc[i]))
//                setfa(u,sc[i]);
//        }
//        sc.pop_back();
        
        //每次都要算集合数,超时了!!!
//        for(int i=1;i<=n;i++){
//            if(count(sc.begin(),sc.end(),i)) continue;
//            e[getfa(i)]++;
//        }
//        ans.push_back(e.size());
//        e.clear();

		
    }
    
    for(int i=k;i>=0;i--){
        cout<<ans[i]<<endl;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值