当前位置:   article > 正文

三元环计数(HDU 6104)

竞赛图三元环计数

三元环计数


这个三元环计数就是去计算图里面有多少个三元环。计算这个数目在有的题目里面有很重要的作用。看了一些博客后总结一下。有错误望指正

1. 三元环

三元环就是图中所示的样子
1596795-20190825161906196-66679285.png

2.如何判断是三元环?

三元环现实中我们一下就可以判断出来,但是竞赛中要怎么判断呢??

我们都知道一句话:敌人的敌人就是我们的朋友 ,对三元环的判断基本就是这样的。如果我们知道a b间连边,b c间连边,只要根据a c之间是否连边就可以判断这三个点能不能构成三元环。

3.具体做法:

上面的只是抽象的理解,具体的做法如下:(不着急我们一步一步来)

(1) 只看如上图三点 :

  1. 如果只看上面三点的话,第一步就是标记a访问过(vis[a]=1)。并将与 a 所连的所有点的前置点指向a(即 pre[b]=a ,pre[c]=a )。
  2. 遍历与a相连的所有点,假设先遍历到b,再遍历b的所有相连的点这里是c,这样我们相当于确定a为自己,b为自己的敌人,c为b的敌人,其实这个比喻不太恰当,判断三点能不能构成三元环主要看a c是否连边(即看pre[c]是否等于a )。发现pre[c]==a,所以cnt++( cnt记录三元环个数)。
  3. 之后重复对每个点进行操作。

(2)多个点:

  1. 原理就是上面的,具体做法见下面。

  2. 首先要做一些准备工作,记录每个点的入度,还有hash (用于之后操作的判断连点是否相连) ,将所有的点分为两类 : 1. 入度 <=sqrt(m) 的点 2. 入度>sqrt(m) 的点(这一步只需要一个if判断就好了)。

  3. 做完上面的准备我们就可以遍历图中每一个点让它作为a点, 然后遍历它的所有相连的点设为b,赋值pre[edge[a] [i]]=a(即这个点与a连边), 对于所有b点,

    • 如果是第一类点就像(1)中那样处理,具体见代码会好理解点。因为mm条边最多每条边遍历一次,然后暴力的点的入度<=sqrt(m)<=sqrt(m),所以复杂度 O(msqrt(m))。

    • 如果是第二类点就直接暴力任意三个点,判断这三个点是否构成环,因为这一类点的个数不会超过sqrt(m)sqrt(m)个,所以复杂度约为O(sqrt(m)3) = O(msqrt(m))。


    下面是第二类点暴力判三点的方法:

  4. 至于上面如何判断三点构成环可以map,hash, set都可以,这里我用set(具体操作也是映射的原理),如果u,v两点之间连边那么set里面就添加 1ll * u bas+v , 和 1ll v * bas + u ( 这里bas=1e5+1)。判断时就是,现在我们不是有a,b两点对吧,然后b度数较大用第二种方法,这时我们遍历与a相连的所有点c,根据前面的描述肯定ab,ac之间连边了,那么现在只要判断bc之间是否连边就可以判断三点是否构成三元环 (具体即_ hash.find(1ll * b * bas+c) != _ hash.end() ) ,这里_hash即set名,也就是只要再set集合中找的到两点hash的值就说明里连边了。

  5. 如何满足就ans++。具体学习见代码(HDU 6184)


HDU 6184 题目:

给一张n个点m条边的无向图,问有多少个A−structureA−structure
其中A−structureA−structure满足V=(A,B,C,D)V=(A,B,C,D) && E=(AB,BC,CD,DA,AC)E=(AB,BC,CD,DA,AC) 显然A−structureA−structure是由两个有公共边的三元环构成的

对于这道题,我们在求三元环的时候,统计一下每条边有多少对点能构成三元环,C(cnt,2)C(cnt,2)累计一下即可

  1. #include<iostream>
  2. #include<cstring>
  3. #include<cstdio>
  4. #include<queue>
  5. #include<vector>
  6. #include<set>
  7. #include<cmath>
  8. #include<algorithm>
  9. using namespace std;
  10. typedef long long ll;
  11. const int MA=1e5+5;
  12. const int bas=1e5+1;
  13. vector<int>edge[MA];//邻接表存图
  14. set<ll>_hash;//set集合
  15. int n,m;
  16. int vis[MA],du[MA],pre[MA];//分别维护:是否处理过标记,度数,所连点
  17. void init()//初始化
  18. {
  19. for(int i=1;i<=n;++i){
  20. vis[i]=du[i]=pre[i]=0;
  21. edge[i].clear();
  22. }
  23. }
  24. void woke()
  25. {
  26. int x=sqrt(1.0*m);//这就是两类点的区分点sqrt(m)
  27. ll ans=0,cnt;//分别维护答案和每次的三元环个数
  28. for(int a=1;a<=n;++a){//遍历每一个点作为a
  29. vis[a]=1;
  30. for(int i=0;i<edge[a].size();++i)//见步骤3
  31. pre[edge[a][i]]=a;
  32. for(int i=0;i<edge[a].size();++i){
  33. cnt=0;
  34. int b=edge[a][i];
  35. if(vis[b])continue;
  36. if(du[b]<=x){//第一类点的处理
  37. for(int j=0;j<edge[b].size();++j){
  38. int c=edge[b][j];
  39. if(pre[c]==a) ++cnt;//就是三个点时的判断方法
  40. }
  41. }
  42. else{//第二类点的处理
  43. for(int j=0;j<edge[a].size();++j){
  44. int c=edge[a][j];
  45. if(_hash.find(1ll*b*bas+c)!=_hash.end()) ++cnt;//暴力判三点的方法
  46. }
  47. }
  48. ans+=(cnt-1)*cnt/2;
  49. }
  50. }
  51. printf("%lld\n",ans);
  52. }
  53. int main()
  54. {
  55. int u,v;
  56. while(~scanf("%d%d",&n,&m)){
  57. init();
  58. _hash.clear();
  59. for(int i=1;i<=m;++i){//准备
  60. scanf("%d%d",&u,&v);
  61. du[u]++,du[v]++;//记录度数
  62. edge[u].push_back(v);//存图
  63. edge[v].push_back(u);
  64. _hash.insert(1ll*u*bas+v);//存相连状态(hash思想)
  65. _hash.insert(1ll*v*bas+u);
  66. }
  67. woke();
  68. }
  69. return 0;
  70. }

还有一些其他方法基本就是在优化,这个方法复杂度O(msqrt(m)) 。下面是一些学习博客,可以看看。

推荐

方法1为本方法,方法2是个更优的方法

找三元环

求无向图三元环个数,方法2是更优的方法 O(msqrt(m))

转载于:https://www.cnblogs.com/A-sc/p/11408268.html

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/64644
推荐阅读
相关标签
  

闽ICP备14008679号