首页 > 试题广场 >

第一题

[编程题]第一题
  • 热度指数:16467 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 32M,其他语言64M
  • 算法知识视频讲解
该题的目的是要你统计图的连通分支数。

输入描述:
每个输入文件包含若干行,每行两个整数i,j,表示节点i和j之间存在一条边。
(请使用循环输入,可以参考该网址:https://www.nowcoder.com/discuss/276)


输出描述:
输出每个图的联通分支数。
示例1

输入

1 4
4 3
5 5

输出

2
#include<bits/stdc++.h>
using namespace std;
/*并查集,开辟一个大数组*/
const int MAXN=1000001;
int father[MAXN];
int appear[MAXN];//1表示appear[i]出现过
void Initial()
{
    for(int i=0;i<MAXN;i++)
    {
        father[i]=-1;
        appear[i]=0;
    }
}
int Find(int x)
{
    while(father[x]!=-1)
        x=father[x];
    return x;
}
void Union(int x,int y)//根节点的合并集合
{
    x=Find(x);
    y=Find(y);
    if(x!=y)
       father[y]=x;
    return ;
}
int main()
{
    int x,y;
    Initial();
    while(cin>>x>>y)
    {
        Union(x,y);
        appear[x]=1;
        appear[y]=1;
        //x和y出现过则置为1
    }
    int component=0;
    for(int i=0;i<MAXN;i++)
        if(appear[i]==1&&father[i]==-1)
            component++;
    //因为之前合并过集合所以father[i]==-1说明有一个连通分量,且要出现过
    cout<<component<<endl;
    return 0;
}

发表于 2022-03-30 11:06:30 回复(0)
#include<iostream>
#include<cstdio>
#include<queue>
#include<cmath>
#include<algorithm>
#include<cstring>
#include <sstream>
#include<map>
using namespace std;
int fa[1000005];
bool vis[1000005];
int get(int root){
    if(fa[root]==root)
        return root;
    else
        return fa[root]=get(fa[root]);
}
int main(){
    int a,b;
    memset(vis,0,sizeof(vis));
    for(int i=0;i<=1000000;i++)
        fa[i]=i;
    while(cin>>a>>b){
        vis[a]=true;
        vis[b]=true;
        a=get(a);
        b=get(b);
        fa[a]=b;
    }
    int ans=0;
    for(int i=0;i<=1000000;i++){
        if(vis[i]==true){

            if(fa[i]==i)
                ans++;
        }
    }
    cout<<ans<<endl;
}

发表于 2021-02-06 11:09:49 回复(0)
/*
*根据输入的数据生成并查集 , 然后查询根节点的个数
*/


#include<bits/stdc++.h>

using namespace std;

const int maxn = 1e6;
int father[maxn+5];

void Initial()   //初始化父节点数组
{
    for(int i = 0;i < maxn; i++) father[i] = i;
}

int getFather(int x)   //找到根节点
{
    int a = x;
    while(x != father[x]) x = father[x];
    //路径压缩
    while(a != father[a])
    {
        int z = a; a = father[a]; father[z] = x;
    }
    return x;
}

void Union(int a, int b)   //合并
{
    int fa = getFather(a);
    int fb = getFather(b);
    if(fa != fb) father[fa] = fb;
}

int main()
{
    ios::sync_with_stdio(false);
    int a, b; set<int> s;
    Initial();
    while(cin >> a >> b)
    {
        Union(a, b); s.insert(a); s.insert(b);
    }

    int ans = 0;   
    for(int i : s) ans += father[i] == i? 1 : 0;
    cout << ans << "\n";

    return 0;
}

发表于 2021-01-19 16:04:29 回复(0)
并查集思想找连通分支个数
#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <vector>
#include <map>
using namespace std;

map <int,int> table;
int find(int x){
	if(table.find(x)==table.end()||table[x]==x){
		return x;
	}
	else{
		table[x]=find(table[x]);  //路径压缩 
	}
	return table[x]
}

int main()
{
   int a,b;
   while(cin>>a>>b){  //读入数据,构建并查集 
       if(a==0){
       	break;
	   }
       a=find(a);
       b=find(b);
       table[a]=b;
       table[b]=b;
   }
   map<int,int>::iterator iter;
   int count=0;
   for(iter=table.begin();iter!=table.end();iter++){
   	   if(iter->second==iter->first){
   	   	    count++;
		}
   }
   cout<<count<<'\n';
}


发表于 2020-02-18 17:17:14 回复(2)
这个题只有题目输入的点才有效,于是我们可以只考虑有效点;同时,点的编号与答案无关,为了防止vis过大,用map把输入的点编号 a 转化为第 i 个点,这样vis数组大小与有效点个数相等。由于未知数据量,为了节约空间,使用基于map<int, set<int> >的邻接表。最后找连通分支数,只需要dfs遍历即可。
#include<bits/stdc++.h>

using namespace std;

void dfs(bool vis[], map<int, set<int> > &ms, int i)
{
    vis[i] = 1;
    for(auto mi=ms[i].begin(); mi!=ms[i].end(); mi++){
        if(!vis[*mi]){
            dfs(vis, ms, *mi);
        }
    }
}

int main()
{
    map<int, int> m;
    map<int, set<int> > ms;
    int a, b, cnt = 0;
    while(cin>>a>>b){
        if(m.find(a) == m.end()){
            m[a] = cnt++;
            set<int> s;
            ms[m[a]] = s;
        }
        if(m.find(b) == m.end()){
            m[b] = cnt++;
            set<int> s;
            ms[m[b]] = s;
        }

        if(a != b){
            ms[m[a]].insert(m[b]);
            ms[m[b]].insert(m[a]);
        }
    }

    bool vis[cnt];
    memset(vis, 0, sizeof(vis));

    int res = 0;
    for(int i=0; i<cnt; i++){
        if(!vis[i]){
            res++;
            dfs(vis, ms, i);
        }
    }
    cout<<res;

    return 0;
}


发表于 2020-01-27 21:57:01 回复(0)
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <string>
#include <iostream>
#include <stack>
#include <cmath>
#include <map>
#include <vector>
#include <set>
using namespace std;
map<int, bool> visit;
map<int, vector<int> > mp;
void dfs(int x)
{
    visit[x] = 1;
    for(int i = 0;i<mp[x].size();i++)
    {
        if(!visit[mp[x][i]])
        dfs(mp[x][i]);
    }
}
int main()
{
    visit.clear();
    mp.clear();
    set<int> st;
    st.clear();
    int x,y;
    while(cin >> x >> y)
    {
        st.insert(x);
        st.insert(y);
        mp[x].push_back(y);
        mp[y].push_back(x);
    }
    int ans = 0 ;
    for(set<int>::iterator it = st.begin() ; it != st.end(); ++it)
    {
        if(visit[*it] == 0)
        {
            ans++;
            dfs(*it);
        }
    }
    cout << ans << endl;
}

发表于 2019-02-23 13:50:21 回复(0)
/*
DFS
1.只有有边的点参与计算联通分支数,所以将vis默认为true,当有输入边时,设顶点为false,就可以只考虑有边的顶点的DFS。
2.顶点最大数量MAXN至少设置1000010。100010都不行。
*/
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
const int MAXN=1000010;
vector<int> Adj[MAXN];
bool vis[MAXN];
void DFS(int u)
{
    vis[u]=true;
    for(int i=0;i<Adj[u].size();i++)
    {
        int v=Adj[u][i];
        if(vis[v]==false)
            DFS(v);
    }
}
int main()
{
    fill(vis,vis+MAXN,true);
    int v1,v2;
    while(scanf("%d%d",&v1,&v2)!=EOF)
    {
        Adj[v1].push_back(v2);
        Adj[v2].push_back(v1);
        //只有有边的点参与计算联通分支数
        vis[v1]=vis[v2]=false;
    }
    int count=0;
    for(int i=0;i<MAXN;i++)
    {
        if(vis[i]==false)
        {
            DFS(i);
            count++;
        }
    }
    printf("%d\n",count);
    return 0;
}

发表于 2019-03-07 17:54:38 回复(1)
注意:顶点就是题目中所给的那些,不多也不少,一开始没有弄明白这个一直做不出来
使用了set,保证了元素有序和不重复,便于最后并查集的查找
#include <iostream>
#include <set>
using namespace std;
#define N 1000000
int Tree[N];
int findRoot(int x){
    if(Tree[x]==-1) return x;
    int temp=findRoot(Tree[x]);
    Tree[x]=temp;
    return temp;
}
int main(){
    int a,b;
    set<int>s;
    for(int i=0;i<N;i++) Tree[i]=-1;
    while(cin>>a>>b){
        s.insert(a);
        s.insert(b);
        a=findRoot(a);
        b=findRoot(b);
        if(a!=b) Tree[a]=b;
    }
    int ans=0;
    set<int>::iterator it;
    for(it=s.begin ();it!=s.end ();it++)
        if(Tree[*it]==-1) ans++;
    cout<<ans<<endl;
    return 0;
}

发表于 2019-03-06 16:59:43 回复(1)
顶点数目必须足够大  啊啊啊!!!
//const int MAX=1000010;
#include<iostream>
(720)#include<cstdio>

using namespace std;

const int MAX=1000010;
int father[MAX];
int height[MAX];
bool visit[MAX];

void Init(){
    for(int i=1;i<MAX;i++){
        father[i]=i;
        height[i]=0;
        visit[MAX]=false;
    }
}

int Find(int x){
    if(x!=father[x]){
        father[x]=Find(father[x]);
    }
    return father[x];
}
void Union(int x,int y){
    x=Find(x);
    y=Find(y);
    if(x!=y){
        if(height[x]<height[y]){
            father[x]=y;
        }else if(height[x]>height[y]){
            father[y]=x;
        }else{
            father[y]=x;
            height[x]++;
        }
    }
}

int main(){
    Init();
    int MaxNum=0;
    int x,y;
    while(scanf("%d%d",&x,&y)!=EOF){
        
        Union(x,y);
        visit[x]=true;
        visit[y]=true;
        MaxNum=MaxNum>x?MaxNum:x;//为了缩小下面的遍历范围 保留这一个文件中最大编号的节点
        MaxNum=MaxNum>y?MaxNum:y;
    }
    int component=0;
    for(int i=1;i<=MaxNum;i++){
        if(!visit[i])
            continue;
        if(father[i]==i)
            component++;
    }
    printf("%d\n",component);
    return 0;
}


发表于 2020-03-10 10:41:26 回复(1)
并查集的板子,一开始不会做,后来多做了几道就差不多会了。
普通并查集只是顺序的节点号。但是本题给出的节点号不是顺序的,所以这里没有采用数组的方式,而是采用了map散列表的方式。当然通过dfs也可以很好的解决这个题目。通过对所有节点进行dfs。只要没有全遍历到,那就sum++;
#include<iostream>
#include<vector>
#include<map>
#include<algorithm>
using namespace std;
map<int,int> father;  //通过散列表map实现的father数组
map<int,int> height;  //记录每个节点的高度
int find(int x){
    if(father.find(x)!=father.end()){
        if(father[x]!=x)
            father[x]=find(father[x]);  //路径压缩(最后自己通过例子模拟下过程)
    }
    else{//如果还没有出现的新节点。把father设成他自己(表示根节点),height设成0
        father[x]=x;
        height[x]=0;
    }
    return father[x];
}
void Union(int a,int b){//合并函数
    a=find(a);
    b=find(b);
    if(a!=b){
        if(height[a]>height[b])
            father[b]=a;
        else if(height[b]>height[a])
            father[a]=b;
        else{
            father[a]=b;
            height[a]++;
        }
    }
}
int main()
{
    int i,j;
    while(cin>>i>>j){
        //if(i==0)break;
        Union(i,j);
    }
    int sum=0;
    for(auto it=father.begin();it!=father.end();it++){
        if(it->first==it->second)sum++;  //只要有一个父亲是本身的就说明是根节点
    }
    cout<<sum<<endl;
    return 0;   
}


编辑于 2020-02-21 13:13:57 回复(0)
应该是并查集里面比较简单的,模仿大佬的写了一下,对小白比较友好的写法
#include <iostream>
#include<map>

using namespace std;

map<int, int>mp;
//map用来映射每个顶点对应的父节点是什么

int find(int x) {
    //用于找到父节点以及路径压缩
    if (x != mp[x])
    mp[x] = find(mp[x]);
    return mp[x];
}

void union_two(int A, int B) {
    int c1 = find(A);
    int c2 = find(B);
    if (c1 != c2)
        mp[c2] = c1; //若相连接的两个结点根结点不同,则合并两个根结点
}

int main() {
//这是简单的并查集的运用,因为序号不是连续的则用map映射来实现,表示存储对应的连接关系
    int a, b;
    while (cin >> a >> b) {
        if (mp.find(a) ==mp.end()) { //若已有映射中无对应的结点则存入顶点信息,并将其根节点设为自己
            mp[a] = a;
        }
        if (mp.find(b) ==mp.end()) {
            mp[b] = b;
        }
        union_two(a, b);
    }
    int cnt = 0;
//处理完所有的信息之后,再判断联通分支数为多少,其为该顶点的编号与对应父节点的编号相同的顶点的数量
    for (auto it : mp) {
        if (it.first==it.second)
            cnt++;
    }
    cout << cnt << endl;
    return 0;
}



编辑于 2024-01-19 13:05:47 回复(0)
这道题我的思路就是利用DFS性质,跟那道判断图是否是连通的思路一样,只不过这题是统计数目。
坑点:1.这道题给定的顶点不是按序的,用数组存储会有一定问题,所以我采用map来替换visited数组
         2.这道题顶点数目超级大,所以绝不能用邻接矩阵存储,另外因为我用了map,一开始按其他人讨论大小开邻接表结果内存超限了,后来测试发现只要开大小为500000的就够了
#include<bits/stdc++.h>
using namespace std;
vector<int> adj[500000];
map<int,int> mp;
void dfs(int u){
    mp[u]=1;
    for(int i=0;i<adj[u].size();i++){
        if(mp[adj[u][i]]==0){
            dfs(adj[u][i]);
        }
    }
}
void dfsTravel(int& count){
    for(map<int,int>::iterator it=mp.begin();it!=mp.end();it++){
        if(it->second==0){
            dfs(it->first);
            count++;
        }
    }
}
int main(){
    int i,j;
    //memset(visited,0,sizeof(visited));
    while(cin>>i>>j){
        adj[i].push_back(j);
        adj[j].push_back(i);
        mp[i]=mp[j]=0;
    }
    int count=0;
    dfsTravel(count);
    cout<<count<<endl;
    return 0;
}


发表于 2020-04-20 08:39:05 回复(0)
/*题目有点SB,连个节点范围也不给说
 思路很简单,判断连通分量,直接用并查集就行。
 有几个根节点(father[i]==i),就有几个联通分量
*/
# include<stdio.h>
const int maxn=1000010;
bool isnode[maxn];      //记录是节点的数字
int father[maxn];    
int findfather(int x){
    while(father[x]!=x)
        x=father[x];
    return x;
}
void Union(int a,int b){
    int faA=findfather(a);
    int faB=findfather(b);
    if(faA!=faB){
        father[faA]=faB;
    }
}
int main(){
    int i,j;
    int a,b;
    for(i=0;i<maxn;i++){
        isnode[i]=false;
        father[i]=i;
    }
    while(scanf("%d%d",&a,&b)!=EOF){
        isnode[a]=true;
        isnode[b]=true;
        Union(a,b);
       
    }
      int component=0;
    for(j=0;j<maxn;j++){
        if(isnode[j]==false)  //不是节点的数字直接跳过。
            continue;
        if(father[j]==j)   //在节点中,找出根节点给个数
            component++;
    }
    printf("%d",component);
   
    return 0;
}
发表于 2020-03-18 11:25:14 回复(0)

DFS 染色,一次染一个联通集

#include <functional>
#include <iostream>
#include <unordered_map>
#include <unordered_set>
#include <vector>
using namespace std;

int main() {
    unordered_map<int, vector<int>> g;
    unordered_set<int> st;
    int a, b;
    while (cin >> a >> b) {
        st.insert(a);
        st.insert(b);
        if (a == b) continue;
        g[a].push_back(b);
        g[b].push_back(a);
    }

    unordered_map<int, bool> vis;
    vector<int> nodes(st.begin(), st.end());
    for (auto& [k, v]: g) vis[k] = false;

    function<void(int, int)> dfs = [&](int cur, int pre) {
        if (vis[cur] == true) return;
        vis[cur] = true;
        for (auto nxt: g[cur]) {
            if (nxt != pre && !vis[nxt]) {
                dfs(nxt, cur);
            }
        }
    };

    int ans = 0;
    for (int & node : nodes) {
        if (!vis[node]) {
            dfs(node, -1);
            ans++;
        }
    }
    cout << ans;
}
发表于 2025-07-13 16:27:48 回复(0)

正经做法

基本就是并查集板子,路径压缩+按秩合并了下,优化了下时间。用了set来进行存储节点编号,好操作一点
#include <iostream>
#include <vector>
#include <cstdio>
#include <cmath>
#include <set>

using namespace std;

int top[2000001], setRank[2000001]; //数据范围一定要够大,有点毒瘤了
set<int> nodeSet;

void init(int x) {
    for(int i = 1; i <= x; i++) {
        top[i] = i;
        setRank[i] = 0;
    }
}

//非递归写法,常数有所优化
int find(int x)
{
    while(top[x] != x)
    {
        int t = x;
        x = top[x];
        top[t] = top[x];
    }
    return x;
}


void set_union(int x, int y) {
    int fx = find(x), fy = find(y);
    if(fx != fy) {
        if(setRank[fx] > setRank[fy]) top[fy] = fx;
        else if(setRank[fx] < setRank[fy]) top[fx] = fy;
        else top[fy] = fx, setRank[fx] ++;
    }
}

int main() {
    int m, n;
    int max_num = -1;
    init(2000000);
    while(cin >> m >> n) {
        nodeSet.insert(m);
        nodeSet.insert(n);
        set_union(m, n);
    }
    int cnt = 0;
    for(auto i : nodeSet) {
        if(find(i) == i) cnt++;
    }
    cout << cnt << endl;
	return 0;
}

不正经做法

当我以为这题已经完成的时候看到排行榜上神秘的1ms代码我人是晕的,经过简单的观察发现这题就只有一个数据点,那么是时候使用歪门邪道了——打印答案
用了rust看看能不能让打印快点(
use std::io::{self, *};
 
fn main() {
    println!("26202");
}


发表于 2025-04-03 18:47:32 回复(0)
并查集
#include <bits/stdc++.h>
using namespace std;

const int N = 1000010;
vector<int> father(N, 0);

int find(int u){
    return father[u] == -1 ? u : father[u] = find(father[u]);
}

void join(int u, int v){
    u = find(u);
    v = find(v);
    if( u == v) return;
    else father[v] = u;
}

int main() {
    int u, v;

    while(cin>>u>>v){
        if(father[u] == 0) father[u] = -1;
        if(father[v] == 0) father[v] = -1;
        join(u, v);
    }

    int count = 0;
    for(int i = 0; i < N; i++){
        if(father[i] == -1) count++;
    }
    cout<<count<<endl;
}

发表于 2025-03-08 09:01:01 回复(0)
#include <cstdint>
#include <iostream>
#include<vector>
#include<map>
using namespace std;
map<int,int>node;
struct pairnode{
    int first;
    int second;
    pairnode(int _first,int _second){
        first=_first;
        second=_second;
    }
};
void Init(int visit[],int n){
    for(int i=0;i<n;i++){
        visit[i]=i;
    }
}
int findfather(int visit[],int n){
    if(visit[n]==n){
        return n;
    }
    else return findfather(visit,visit[n]);
}
int main() {
    vector<pairnode> v;
    int a, b;
    int i=0;
    int u, w;
    while (cin >> a >> b) {
        if (node.count(a) == 0) {
            node.insert({a,i++});
        }
        if (node.count(b) == 0) {
            node.insert({b,i++});
        }
        pairnode p(a, b);
        v.push_back(p);
    }
    int setnode = node.size();
    int visit[setnode];
    Init(visit,setnode);
    for (i = 0; i < v.size(); i++) {
        u = v[i].first;
        w = v[i].second;
        int uroot = findfather(visit,node[u]);
        int wroot = findfather(visit,node[w]);
        if (uroot != wroot) {
            setnode--;
            visit[wroot]=uroot;
        }
    }
    cout << setnode << endl;
    return 0;
}

发表于 2025-02-18 18:58:07 回复(0)
#include <stdio.h>
#include <map>
using namespace std;

void initial(int id, map<int, int> &Set)
{
    if (Set.find(id) == Set.end()) // 为空就初始化
    {
        Set[id] = id; // 指向根;
    }
}

int Find(int id, map<int, int> &Set)
{
    if (Set[id] == id)
    {
        return id;
    }
    else
    {
        int root = Find(Set[id], Set);
        Set[id] = root;
        return root;
    }
}
void Union(int u, int v, map<int, int> &Set)
{
    int uroot = Find(u, Set);
    int vroot = Find(v, Set);
    if (uroot != vroot)
    {
        Set[uroot] = vroot;
    }
}

int main()
{
    map<int, int> Set;
    int m, n;
    int SetCount = 0;
    while (scanf("%d %d", &m, &n) != EOF)
    {
        initial(m, Set);
        initial(n, Set);
        Union(m, n, Set);
    }
    map<int, int>::iterator it;
    for (it = Set.begin(); it != Set.end(); it++)
    {
        if (it->first == it->second)
        {
            SetCount++;
        }
    }
    printf("%d\n", SetCount);
    return 0;
}
发表于 2025-02-09 00:03:47 回复(0)
虽然运行时间长,但是能用并查集板子啊,注意题目中可能结点超过几万个
#include <iostream>
#include <cstdio>
#include <map>
using namespace std;
int parent[1000000];
map<int, int> ::iterator it;
void innital(int n, map<int, int>& nodes) {//nodes在主函数,注意传参
    it = nodes.find(n);
    if (it == nodes.end()) {
        nodes[n] = 1;
        parent[n] = -1;
    }
}
int findroot(int x) {
    while (parent[x] != -1) x = parent[x];
    return x;
}
int unionset(int n1, int n2, int parent[1000000]) {
    int n1_root, n2_root;
    n1_root = findroot(n1);
    n2_root = findroot(n2);
    if (n1_root == n2_root) return 0; //同集合不用合并
    else {
        parent[n1_root] = n2_root;
        return 1;
    }
}

int main() {//统计入度为0的节点的数量。如果入度为0的节点数量大于1,则不满足树的定义。检查是否有环路存在。
    int  n1, n2;
    int k = 0;
    int flag = 1;
    map<int, int> nodes;
    map<int, int> degree;
    while (cin >> n1 >> n2) {        
            innital(n1, nodes);
            innital(n2, nodes);
            unionset(n1, n2, parent); //集合合并,合并失败有环
    }
    
        int j = 0;
        for (it = nodes.begin(); it != nodes.end(); it++) {
            int n=it->first;
            if (findroot(n)==n) j++;
        }
        cout<<j<<endl;
    return 0;
}





编辑于 2024-03-23 18:46:02 回复(0)
import sys

nodes = set()
edges = []

for line in sys.stdin:
    start, end = list(map(int, line.strip().split()))
    nodes.add(start)
    nodes.add(end)
    edges.append([start, end])
    edges.append([end, start])

nodes = list(nodes)
adjusts = {}
isvisited = {}
for node in nodes:
    adjusts[node] = []
    isvisited[node] = False

for start, end in edges:
    if start == end:
        pass
    else:
        adjusts[start].append(end)

count = 0
for node in nodes:
    if not isvisited[node]:
        count += 1
        queue = [node]
        while len(queue) > 0:
            cur = queue[0]
            queue.pop(0)
            for next_node in adjusts[cur]:
                if not isvisited[next_node]:
                    queue.append(next_node)
                    isvisited[next_node] = True

print(count)

发表于 2024-03-20 23:21:25 回复(0)