链表加bfs求补图联通块

https://oj.neu.edu.cn/problem/1387

给一个点数N <= 100000, 边 <= 1000000的无向图,求补图的联通块数,以及每个块包含的点数

由于点数太大,补图会是稠密图,甚至建立补图都要O(n^2),只能挖掘一下联通块,bfs,补图的性质,从原图入手求补图的联通块:

在原图中不直接相邻的点,在补图中一定属于同一个联通块

每个点只属于一个联通块,所以找好一个联通块之后可以删去这个联通块的所有点,把图规模缩小

这样子:1.准备一个集合放所有未探索的点,初始化时将1~N放进去

2.从集合中取一点放入队列(新的联通块)

3.当队列不为空时,从队列中取一个点u并弹出,将原图中与u直接相连的点标记;遍历集合,将在集合中的(即未探索的)并且未被标记的点(这些点属于本联通块)入队并从集合中删去,将标记删去。重复执行直到队列为空

4.集合不为空转2,为空结束

考虑有删除操作和时间问题,集合的实现当然是选择链表,用数组实现的双向链表即可 

优化有两个:一是通过原图找补图的联通块;二是把搜过的点删除,这样每次找未标记的点时比起从1循环到N更优(常数优化(误))

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int maxn = 1e5+100, maxm = 1e6+100, inf = 0x3f3f3f3f;
struct lnk{
    int val;
    int pre, nxt;
}lk[maxn];
struct edge{
    int v, nxt;
}e[maxm*2];
int head[maxn], tot, block_cnt, n, m;
int adj[maxn], vis[maxn], num[maxn];
void addedge(int u, int v){
    e[tot] = (edge){v, head[u]};
    head[u] = tot++;
}
void dele(int x){
    lk[lk[x].nxt].pre = lk[x].pre;
    lk[lk[x].pre].nxt = lk[x].nxt;
}
void src(){
    for(int i = 1; i <= n; i++){
        vis[i] = adj[i] = 0;
    }
    queue<int>Q;
    block_cnt = 0;
    while(lk[0].nxt != -1){
        //puts("blk++");
        Q.push(lk[0].nxt);
        //printf("take %d\n", lk[0].nxt);
        vis[lk[lk[0].nxt].val] = 1;
        dele(lk[0].nxt);
        block_cnt++;
        num[block_cnt] = 1;
        while(!Q.empty()){
            int x = Q.front();
            x = lk[x].val;
            //printf("%d\n", x);
            Q.pop();
            for(int i = head[x]; ~i; i = e[i].nxt){
                int v = e[i].v;
                adj[v] = 1;
            }
            for(int i = lk[0].nxt; ~i; i = lk[i].nxt){
                int w = lk[i].val;
                if(!vis[w] && !adj[w]){
                    Q.push(w);
                    vis[w] = 1;
                    dele(i);
                    num[block_cnt]++;
                }
            }
            for(int i = head[x]; ~i; i = e[i].nxt){
                int v = e[i].v;
                adj[v] = 0;
            }
        }
    }
}
int main(){
    int t;
    scanf("%d", &t);
    while(t--){
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; i++)
            head[i] = -1;
        tot = 0;
        while(m--){
            int u, v;
            scanf("%d%d", &u, &v);
            addedge(u, v);
            addedge(v, u);
        }
        for(int i = 1; i <= n; i++){
            lk[i].val = i;
            lk[i].pre = i-1;
            lk[i].nxt = i+1;
        }
        lk[n].nxt = -1;
        lk[0].nxt = 1;
        src();
        sort(num+1, num+block_cnt+1);
        printf("%d\n", block_cnt);
        for(int i = 1; i <= block_cnt; i++){
            printf("%d%c", num[i], i == block_cnt ? ‘\n‘ : ‘ ‘);
        }
    }
    return 0;
}
/*
 3
 5 7
 1 2
 1 3
 1 4
 1 5
 2 3
 2 4
 2 5
 6 9
 1 4 1 5 1 6
 2 4 2 5 2 6
 3 4 3 5 3 6
 3 3
 1 2 2 3 3 1

*/

原文地址:https://www.cnblogs.com/DearDongchen/p/9211513.html

时间: 2024-10-17 10:19:16

链表加bfs求补图联通块的相关文章

Codeforces Round #369 (Div. 2) D. Directed Roads dfs求某个联通块的在环上的点的数量

D. Directed Roads ZS the Coder and Chris the Baboon has explored Udayland for quite some time. They realize that it consists of n towns numbered from 1to n. There are n directed roads in the Udayland. i-th of them goes from town i to some other town 

分别利用并查集,DFS和BFS方法求联通块的数量

联通块是指给定n个点,输入a,b(1<=a,b<=n),然后将a,b连接,凡是连接在一起的所有数就是一个联通块: 题意:第一行输入n,m,分别表示有n个数,有输入m对连接点,以下将要输入m行(输入数据到文件截止): 输出:第一行要求输出联通块的个数,并在第二行分别输出每个联通块中点的数量,每个数之间以一个空格隔开. 样例 15 31 42 53 5输出:2 2 3样列2 9 81 22 33 43 74 54 67 87 9输出: 19 如果不明白的话可以画图试试,最多花半个小时,要是早这样不

洛谷P1141 //bfs求无向图的子连通块大小(多次询问)

http://www.luogu.org/problem/show?pid=1141 多询问题,求无向图的子连通块大小. 直接bfs,读一个搜一个,过60: 100%的点1,000,000个点,100,000次询问,显然是记忆化. 我很弱,最开始开了个数组记录每个点属于的连通块的第一个点的坐标,然后写了一堆,自己都烦. 后来问某大神,似拨开云雾见到了青天.用cnt记录连通块的" 名字 ". 学会了这一招,不代表过了. 我还是读一个点,如果访问过就输出,没访问就dfs,然后每dfs就循环

【UVA10765】Doves and bombs (BCC求割点后联通块数量)

题目: 题意: 给了一个联通无向图,现在问去掉某个点,会让图变成几个联通块? 输出的按分出的从多到小,若相等,输出标号从小到大.输出M个. 分析: BCC求割点后联通块数量,Tarjan算法. 联通块的数目在找到一个low[y]>=dfn[x]时累加,最后加一即可. 代码如下: 1 #include<cstdio> 2 #include<cstdlib> 3 #include<cstring> 4 #include<iostream> 5 #inclu

C. Learning Languages 求联通块的个数

C. Learning Languages 1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <cmath> 5 #include <algorithm> 6 #include <string> 7 #include <vector> 8 #include <stack> 9 #include <queue&

HDU 4738 Caocao&#39;s Bridges ——(找桥,求联通块)

题意:给你一个无向图,给你一个炸弹去炸掉一条边,使得整个图不再联通,你需要派人去安置炸弹,且派去的人至少要比这条边上的人多.问至少要派去多少个,如果没法完成,就输出-1. 分析:如果这个图是已经是多个联通块了,那么一个人都不用去,如果不是,那么只要找出这个无向图上的桥并且哨兵数量最少的那座把它炸了就行(输出这条边上的哨兵数量即可).直接tarjan就可以写. 注意点:1.可能有重边,所以用手写邻接表的方式存图:2.如果一座桥上没有哨兵,那么你也得至少派去一个人去安置炸弹(因为炸弹不会自己飞过去啊

洛谷OJ 1141 01迷宫 暴力(求点所在的联通块大小)

https://www.luogu.org/problem/show?pid=1141 题意:n*n地图,0可以走到相邻的某个1上,1可以走到相邻的某个0上,m次询问,问(x,y)能走到多少个格子? n<=1e3,m<=1e5.若点a能到b,则b也能到a.显然a,b能到达的点相同,同一个联通块内的ans是相同的,即求点所在的连通块大小 #include <bits/stdc++.h> using namespace std; const int N=2e3+20; int dx[4

利用DFS求联通块个数

/*572 - Oil Deposits ---DFS求联通块个数:从每个@出发遍历它周围的@.每次访问一个格子就给它一个联通编号,在访问之前,先检查他是否 ---已有编号,从而避免了一个格子重复访问多次 --*/ #define _CRT_SECURE_NO_DEPRECATE #include<iostream> #include<string.h> #include<algorithm> using namespace std; const int maxn =

【紫书】Oil Deposits UVA - 572 dfs求联通块

题意:给你一个地图,求联通块的数量. 题解: for(所有还未标记的'@'点) 边dfs边在vis数组标记id,直到不能继续dfs. 输出id及可: ac代码: #define _CRT_SECURE_NO_WARNINGS #include "stdio.h" #include<stdio.h> #include<algorithm> #include<string> #include<vector> #include<list&