zoukankan      html  css  js  c++  java
  • 【科技】三/四元环计数

    【科技】三元环计数

    介绍一个小科技,三元环计数,利用复杂度分析证明暴力求解是科学的。

    具体问题就是,给定一张 n 个点,m 条边的简单无向图,求解无序三元组 (i,j,k)的数量,其中满足存在边 (i,j),(i,k),(j,k)

    分析:

    第一秒想到的是直接暴力枚举,但发现一个环上每个点都会把这个环算上一遍,纵使最后容斥除掉

    但每个点为基础找三元环的复杂度也是很高的

    考虑怎么才能让一个环只被算上一次,这样是最优化的了,再没有别的方法了

    正解:

    我们先把无向图转成有向图

    给每个点定义一个双关键字(deg,id),其中deg表示度数,id表示标号

    这样对于每一对点都能严格比较出大小

    我们把每一条边重定向成从度数大的点连向度数小的点,我们就可以得到一张有向无环图。

    具体怎么找环:

    1. 枚举一个点i,将所有i点连出的点标记为i
    2. 枚举一个点i连出的点j
    3. 枚举一个点j连出的点k,如果k的标记是i,那么就找到了一组三元环(i,j,k)

    分析每一个三元环只会在i这个点被算到一次答案

    可以证明这么做的复杂度的一个上界是O(m√m)

    因为一个三元环在新图上必定只有一个点的出度为2,然后我们只在这个点上更新三元环数量

    code :

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 250005;
    int n, m, ans;
    int a[N], du[N], vi[N], eu[N], ev[N];
    vector<int> g[N];
    inline bool cmp(int x, int y) {
      return du[x] != du[y]? du[x] > du[y] : x < y;
    }
    int main() {
      scanf("%d%d", &n, &m);
      for (int i = 1; i <= n; ++i)scanf("%d", &a[i]);
      for (int i = 1; i <= m; ++i)
          scanf("%d%d", &eu[i], &ev[i]), ++du[eu[i]], ++du[ev[i]];
      for (int i = 1; i <= m; ++i)
        if (cmp(eu[i], ev[i])) g[eu[i]].push_back(ev[i]);
        else g[ev[i]].push_back(eu[i]);
      for (int i = 1; i <= n; ++i) {
        for (int j : g[i]) vi[j] = i;
        for (int j : g[i])
          for (int k : g[j])
            if (vi[k] == i) ++ans;
      }
      printf("%d
    ", ans);
      return 0;
    }
    

    那四元环怎么办?

    类似三元环还是要对边定向。

    但此时注意枚举点 u 相邻的点 v 是原图中的边(而非重定向后的边),

    而枚举 v相邻的点 w 则要是重定向后的点(可以交换图的顺序),

    原因是我们相当于是枚举两个部分拼起来。

    还是在 u 被枚举一次,因为 rk[u]<rk[w]

    code:

    inline bool cmp(const int& _this,const int& _that) 
    	{return d[_this]<d[_that]||(d[_this]==d[_that]&&_this<_that);}
    #define con const int&
    inline void main() {
    	n=g(),m=g(); for(R i=1,u,v;i<=m;++i) 
    		u=g(),v=g(),e[u].push_back(v),e[v].push_back(u);
    	for(R i=1;i<=n;++i) d[id[i]=i]=e[i].size();
    	sort(id+1,id+n+1,cmp);
    	for(R i=1;i<=n;++i) rk[id[i]]=i;
    	for(R u=1;u<=n;++u) for(con v:e[u]) 
    		if(rk[v]>rk[u]) f[u].push_back(v);
    	for(R u=1;u<=n;++u) {
    		for(con v:e[u]) for(con w:f[v]) if(rk[w]>rk[u]) ans+=c[w],++c[w]; //交换e与f的枚举顺序也是对的。
    		for(con v:e[u]) for(con w:f[v]) if(rk[w]>rk[u]) c[w]=0; //清空桶。
    	} printf("%lld
    ",ans);
    } 
    
    
    
  • 相关阅读:
    二叉树的前序、中序、后序遍历
    队列&优先队列
    angularJS 初始化
    angularJS $q
    获取checkbox返回值
    ngRoute
    两个类的装饰器,内置的魔术方法
    super封装property反射
    广度优先和深度优先 父类对子类的约束 多态 鸭子模型
    继承
  • 原文地址:https://www.cnblogs.com/wzxbeliever/p/11792340.html
Copyright © 2011-2022 走看看