acm-无向图三元环、四元环计数

三元环计数

考虑对无向图的边进行定向,度数小的点连向度数大的点,如果度数相同则编号小的点连向编号大的点。
然后再这张新图(有向图)中我们枚举所有点 u u u,对于每个点 u u u我们枚举它的出边对应的端点 v v v,先给这些点打上标记,然后再枚举 u u u的出边对应的端点 v v v,枚举 v v v的出边对应的端点 w w w,如果 w w w是标记点的话就找到一个三元环,每个三元环都一定只会被恰好枚举一次,因此找到一个三元环就 + + a n s ++ans ++ans即可,注意枚举下一个 u u u之前需要清空标记数组。

考虑正确性,经过重定向后新图一定是一张无环的有向图,那么三元环只可能是如下图所示的形式:
acm-无向图三元环、四元环计数_第1张图片
容易发现只有再枚举 A A A的时候能够将该三元环计入答案。

由于枚举 u , v u,v u,v复杂度为 O ( n + m ) O(n+m) O(n+m),考虑对于每个 v v v而言,假设它的出边有 k k k条,那么 d e g [ v ] ≥ k deg[v]\ge k deg[v]k,那么这 k k k条边对应的端点 w w w的度数也一定至少为 k k k,因此 k ∗ k ≤ 2 m ⇒ k = O ( m ) k*k\le 2m\Rightarrow k=O(\sqrt m) kk2mk=O(m ),即总复杂度约为 O ( ( n + m ) m ) O((n+m)\sqrt m) O((n+m)m )

这里以LuoGu P1989 无向图三元环计数为例给出一份参考代码:

#include 
using namespace std;

const int maxn = 1e5+5;
const int maxm = 2e5+5;

struct EDGE{
	int u,v;
}e[maxm];
int vis[maxn],deg[maxn];
vector<int>g[maxn];
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=0;i<m;++i){
		scanf("%d%d",&e[i].u,&e[i].v);
		deg[e[i].u]++;
		deg[e[i].v]++;
	}
	for(int i=0;i<m;++i){
		int u=e[i].u,v=e[i].v;
		if(deg[u]>deg[v])swap(u,v);
		if(deg[u]==deg[v] && u>v)swap(u,v);
		g[u].push_back(v); 
	}
	int ans=0;
	for(int u=1;u<=n;++u){
		for(int v:g[u])vis[v]=1;
		for(int v:g[u])for(int w:g[v])vis[w]&&(ans++);
		for(int v:g[u])vis[v]=0;
	}
	printf("%d\n",ans);
} 

四元环计数

类似于三元环,我们也要对原图进行重定向,不过这里需要保留原图,设 g g g是原图, h h h是新图,我们仍然枚举 u u u,然后再原图中枚举 v v v,然后在新图中枚举 w w w,如果 w w w的优先级大于 u , v u,v u,v,那么先把答案加上 w w w的统计量 c n t [ w ] cnt[w] cnt[w],然后再 c n t [ w ] + + cnt[w]++ cnt[w]++即可。

容易发现复杂度与三元环是一样的,唯一的区别在于标记和统计答案的方式不同。核心原理是把四元环拆成两半,下面给出具体解释。
我们发现 h h h图中的每个四元环一定存在一个端点是如下形式:
acm-无向图三元环、四元环计数_第2张图片
即在这个环中至少有两条边指向同一个顶点,上图中除了这两条边外的另外两条边,假设他们没有指向同一个顶点,比如像下图这样:
acm-无向图三元环、四元环计数_第3张图片
那么可以发现只有枚举到 A A A点的时候才会统计到该四元环,具体来说会先走 A − > B − > C A->B->C A>B>C统计一半,再走 A − > D − > C A->D->C A>D>C统计另一半,注意走第一条边的时候走的是原图的边!如果以 A C AC AC为轴线存在多个四元环,那么按照这种一半一半统计贡献的方式仍然是可以全部计算出来的。另外注意到新图 h h h中存在 A − > B − > C A->B->C A>B>C这样的拓扑链,说明 A A A的优先级是小于 C C C的,因此也能确保循环的能够进入最里层。

再看一种特殊情况,那就是存在两个点都是两条边的指向,如下图所示:
acm-无向图三元环、四元环计数_第4张图片
上图中 A 、 C A、C AC两点都是被两条边所指向的点,好像这个四元环会被统计两次,但实际上不会,因为 A 、 C A、C AC必定存在一个优先级的大小之分(计算度数相同还要比较编号),因此仍然只会被统计一次。

综上来说每个四元环都恰好被一个点统计一次,因此算法是正确的,复杂度分析与三元环的相同,就不多说了。下面给出一份参考代码:

#include 
using namespace std;
const int maxn = 2e5+5;
int d[maxn],id[maxn],rk[maxn],cnt[maxn];
vector<int>g[maxn],h[maxn];

int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=0;i<m;++i){
		int u,v;
		scanf("%d%d",&u,&v);
		g[u].push_back(v);
		g[v].push_back(u);
	}
	for(int i=1;i<=n;++i)d[id[i]=i]=g[i].size();
	sort(id+1,id+1+n,[&](int a,int b){
		return d[a]<d[b] || d[a]==d[b] && a<b; 
	});
	for(int i=1;i<=n;++i)rk[id[i]]=i;
	for(int u=1;u<=n;++u)for(auto v:g[u]){
		if(rk[v]>rk[u])h[u].push_back(v);
	}
	int ans=0;
	for(int u=1;u<=n;++u){
		for(auto v:g[u])for(auto w:h[v])if(rk[w]>rk[u]){ans+=cnt[w]++;}
		for(auto v:g[u])for(auto w:h[v])if(rk[w]>rk[u])cnt[w]=0;
	}
	printf("%d\n",ans);
}/*test:
6 6
1 2
1 3
2 4
3 4
3 6
2 5
*/

你可能感兴趣的:(图论,方案计数,acm竞赛,算法)