无向图三元环 查找/计数

理解

时间复杂度 \(O(M \sqrt{M})\)
作用
求出无向图的所有三元环

过程
首先要对所有的无向边进行定向,对于任何一条边,从度数大的点连向度数小的点,如果度数相同,从编号小的点连向编号大的点。

此时这张图是一个有向无环图。

  • 枚举每一个点u,然后将u的所有相邻的点都标记上“被u访问了”,
  • 然后再枚举u的相邻的点v,
  • 然后再枚举v的相邻的点w,
    如果w存在“被u访问了”的标记,那么(u,v,w)就是一个三元环了,且每个三元环只会被计算一次。

这样为什么是对的呢?
按照算法的流程,原来图中的一个三元环 \(((i,j,k)\) 在重定向之后一定变成了 i 向 j,k 连边,j,k 之间再连一条边的情况,不妨设为\(j \to k\)
我们枚举 i 时会标记 j,k ,再遍历 j找第三个点的时候一定会找到 k 。
并且对于这个环,遍历顺序只可能是 \(i\to j \to k\),也就是我们只能由 i 来找到这个环。每个点我们只会枚举一次,
所以图中的每一个三元环,我们都会遍历 且 仅遍历一次。

那么时间复杂度为什么 \(O(m\sqrt m)\) 呢?

考虑每条边 \((u,v)\) 对时间复杂度的贡献与 \(v\) 的出度同阶,总的时间复杂度应当是 $\sum_{i=1}^m d_v$,这里 \(d\) 是出度。

对于原图中度数不大于 \(\sqrt{m}\) 的点,新图中的度数也不可能更大,所以上界 \(\sqrt{m}\)

对于原图中度数大于 \(\sqrt{m}\) 的点,由于我们只向度数大的点连边,而度数大于 \(\sqrt{m}\) 的点最多有 \(\sqrt{m}\)个,度数也不可能超过 \(\sqrt m\)

综上,总的时间复杂度 \(O(m\sqrt{m})\)

模板代码1

时间复杂度 \(O(M \sqrt{M})\)
将边先存起来,然后再建图。
优点: 代码清晰,好看
缺点: 空间大些

int n,m;
vector<int> g[N];
vector<pii> vec;
int d[N];
int vis[N];
int get_sum(){
    int ans=0;
    for(int i=1;i<=n;i++){
        for(int j: g[i]) vis[j]=1;
        for(int j:g[i])
            for(int k:g[j]) 
                if(vis[k]) ans++;
        for(int j: g[i]) vis[j]=0;
    }
    return ans;
}
void solve(){
    cin>>n>>m;
    for(int i=0;i<m;i++){
        int u,v;cin>>u>>v;
        d[u]++,d[v]++;
        vec.push_back({u,v});
    }
    for(pii t: vec){
        int u=t.first,v=t.second;
        if(d[u]>d[v] ||(d[u]==d[v] && u>v)) swap(u,v);
        g[u].push_back(v);
    }
    cout<<get_sum()<<endl;
}

模板代码2

时间复杂度 \(O(M \sqrt{M})\)
直接建图,然后通过判断限制,达到单向的目的。
优点: 空间优秀
缺点: 代码难看

int n, m;
vector<int> g[N];
int vis[N];
int get_sum()
{
    int ans=0;
    for (int i = 1; i <= n; i++)
    {
        for (int j : g[i])
            if(g[i].size()>g[j].size() || (g[i].size()==g[j].size() && i>j))
                vis[j] = 1;
        for (int j : g[i])
            if(g[i].size()>g[j].size() || (g[i].size()==g[j].size() && i>j))
                for (int k : g[j])
                    if(g[j].size()>g[k].size() || (g[j].size()==g[k].size() && j>k))
                        if (vis[k])
                           ans++;
        for (int j : g[i])
            if(g[i].size()>g[j].size() || (g[i].size()==g[j].size() && i>j))
                vis[j] = 0;
    }
    return ans;
}

void solve(){
    cin>>n>>m;
    for(int i=0;i<m;i++){
        int u,v;cin>>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    cout<<get_sum()<<endl;

}

模板题

P1989 无向图三元环计数

代码

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>  
const int N=1e5+10;
const int mod=1e9+7;
int n,m;
vector<int> g[N];
vector<pii> vec;
int d[N];
int vis[N];
int get_sum(){
    int ans=0;
    for(int i=1;i<=n;i++){
        for(int j: g[i]) vis[j]=1;
        for(int j:g[i])
            for(int k:g[j]) 
                if(vis[k]) ans++;
        for(int j: g[i]) vis[j]=0;
    }
    return ans;
}
void solve(){
    cin>>n>>m;
    for(int i=0;i<m;i++){
        int u,v;cin>>u>>v;
        d[u]++,d[v]++;
        vec.push_back({u,v});
    }
    for(pii t: vec){
        int u=t.first,v=t.second;
        if(d[u]>d[v] ||(d[u]==d[v] && u>v)) swap(u,v);
        g[u].push_back(v);
    }
    cout<<get_sum()<<endl;

}


signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t=1;
    // cin>>t;
    while(t--)
        solve();
    return 0;
}

C - Friend-Graph HDU - 6152

注意及时剪枝结束,不然会超时。
代码

#include <bits/stdc++.h>
using namespace std;
// #define int long long
// #define pii pair<int, int>
//***********************必不可少,不然内存超限
// #define int short int 
const int N = 3010;
const int mod = 1e9 + 7;
int n, m;
vector<int> g[N];
int vis[N];
int get_sum()
{
    for (int i = 1; i <= n; i++)
    {
        for (int j : g[i])
            if(g[i].size()>g[j].size() || (g[i].size()==g[j].size() && i>j))
                vis[j] = 1;
        for (int j : g[i])
            if(g[i].size()>g[j].size() || (g[i].size()==g[j].size() && i>j))
                for (int k : g[j])
                    if(g[j].size()>g[k].size() || (g[j].size()==g[k].size() && j>k))
                        if (vis[k])
                           return 1;
        for (int j : g[i])
            if(g[i].size()>g[j].size() || (g[i].size()==g[j].size() && i>j))
                vis[j] = 0;
    }
    return 0;
}
void init()
{
    m = 0;
    for (int i = 1; i <= n; ++i)
        g[i].clear();
}
void solve()
{
    cin >> n;
    init();
    for (int i = 1; i <= n - 1; i++){
        for (int j = i + 1; j <= n; j++){
            int t;cin >> t;
            if (t){
                m++;
                g[i].push_back(j);
                g[j].push_back(i);
            }
        }
    }
    int ans1 = 0, ans2 = 0;
    ans1 = get_sum();
    m = n * (n - 1) / 2 - m;
    //反向建边
    for (int i = 1; i <= n; ++i)
    {
        memset(vis, 0, sizeof vis);
        for (int j : g[i])
        {
            vis[j] = 1;
        }
        g[i].clear();
        for (int j = 1; j <= n; j++)
        {
            if (vis[j] == 0)
                g[i].push_back(j);
        }
    }
    ans2 = get_sum();
    if (ans1 == 0 && ans2 == 0)
        cout << "Great Team!" << endl;
    else
        cout << "Bad Team!" << endl;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
    cin >> t;

    while (t--)
        solve();
    return 0;
}

原文1:https://blog.51cto.com/u_15072778/3772333
原文2:https://www.luogu.com.cn/blog/i207M/san-yuan-huan-ji-shuo-xue-xi-bi-ji

posted @ 2022-09-07 11:29  kingwzun  阅读(142)  评论(0编辑  收藏  举报