HDU-6184 (无向图三元环计数)

题意:

给一个n个点m条边的无向图,要求统计满足star(四个点五条边的子图)的个数。(2<=n<=1e5, 1<=m<=min(1e5, n*(n-1)/2));

思路:

经分析1个star是由两个有一条相同边的不同的三元环构成的,所以问题就是对三元环计数。统计一个边能构成几个三元环,然后ans += C(cnt, 2)即可。

AC代码:

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int bas = 1e5+1;
const int maxn = 1e5+5;
vector<int> G[maxn];
int n, m;
//unordered_set<LL> _hash;
set<LL> _hash;
int deg[maxn], vis[maxn];
int bel[maxn];
void init()
{
	for(int i = 1; i <= n; ++i)
	{
		deg[i] = bel[i] = vis[i] = 0;
		G[i].clear();
	}
}
void work()
{
	int x = sqrt(1.0*m);
	LL ans = 0, cnt;
	for(int a = 1; a <= n; ++a)
	{
		vis[a] = 1;
		for(int i = 0; i < G[a].size(); ++i)
		bel[G[a][i]] = a;
		for(int i = 0; i < G[a].size(); ++i)
		{
			int b = G[a][i]; cnt = 0;
			if(vis[b]) continue;
			if(deg[b] <= x)
			{
				for(int j = 0; j < G[b].size(); ++j)
				{
					int c = G[b][j];
					if(bel[c] == a) ++cnt;
				}
			}
			else
			{
				for(int j = 0; j < G[a].size(); ++j)
				{
					int c = G[a][j];
					if(_hash.find(1ll*b*bas+c) != _hash.end())
					++cnt;
				}
			}
			ans += (cnt-1)*cnt/2;
		}
	}
	printf("%lld\n", ans);
}
int main()
{
	int t, u, v;
	while(~scanf("%d %d", &n, &m))
	{
		init(); _hash.clear();
		for(int i = 1; i <= m; ++i)
		{
			scanf("%d %d", &u, &v);
			++deg[u], ++deg[v];
			G[u].push_back(v);
			G[v].push_back(u);
			_hash.insert(1ll*u*bas+v);
			_hash.insert(1ll*v*bas+u);
		}
		work();
	}	
	return 0;
}



在此之前,先在网上学了一下统计三元环的代码()

首先我们判断两点是否存在边可以通过unordered_set来hash一下再进行判断。(unordered_set头文件就是<unordered_set>,其复杂度我不是很清楚,但网上查阅说均摊耗时为O(1),但是我如果在下面全部的连边判断都改成unordered_set,交题会超时的。但用于hash,其速度肯定比set快这是毋庸置疑的。)

最直接的就是枚举m条边,然后再枚举这条边度数较小的点的所有连边,判断这个连边的另一个端点是否与度数较大的那个点之间存在边进行统计,由于每个三元环的边都会贡献一次,所以最后结果应/3,当然也可以不重复统计,定一个方向统计就好了,这种暴力的方法其实是O(m*logm)的,但求三元环会超时。

附代码:

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int bas = 1e5+1;
const int maxn = 1e5+5;
const int maxm = maxn*2;
struct node
{
	int u, v, next;
} edge[maxm];
int no, head[maxn];
int n, m;
set<LL> hash;
int deg[maxn];
void init()
{
	no = 0;
	memset(head, -1, sizeof head);
}
inline void add(int u, int v)
{
	edge[no].u = u; edge[no].v = v;
	edge[no].next = head[u];
	head[u] = no++;
}
void ssort(LL &a, LL &b, LL &c)
{
	LL tmp = a+b+c;
	LL aa = max(max(a, b), c);
	LL cc = min(min(a, b), c);
	a = aa, c = cc, b = tmp-aa-cc;
}
void work()
{
	LL ans = 0;
	for(int i = 0; i < no; ++i)
	{
		if(!(i&1) && deg[edge[i].u] > deg[edge[i+1].u]) continue;
		if((i&1) && deg[edge[i].u] >= deg[edge[i-1].u]) continue;
		//两个判断避免下面的重复计算 
		int a = edge[i].u, b = edge[i].v;
		for(int k = head[a]; k+1; k = edge[k].next)
		{
			int c = edge[k].v;
			if(c == b) continue;
			LL aa = a, bb = b, cc = c;
			ssort(aa, bb, cc);
			if(hash.find(aa*bas*bas+bb*bas+cc) != hash.end())
			continue;
			//判重,避免重复统计 
			LL aaa = a, ccc = c;
			if(aaa > ccc) swap(aaa, ccc);
			if(hash.find(aaa*bas+ccc) != hash.end())
			++ans, hash.insert(aa*bas*bas+bb*bas+cc);
		}
	}
	printf("%lld\n", ans);
}
int main()
{
	int t, u, v;
	for(scanf("%d", &t); t--;)
	{
		init(); hash.clear();
		scanf("%d %d", &n, &m);
		for(int i = 1; i <= m; ++i)
		{
			scanf("%d %d", &u, &v);
			++deg[u], ++deg[v];//抄的时候记得前面清空
			if(u > v) swap(u, v);
			add(u, v); add(v, u);
			hash.insert(1ll*u*bas+v);
		}
		work();
	}	
	return 0;
}


另一个方法也是O(m*logm)的,但是却减少了很多判断。

首先假设这个图是个完全图,那么当前图的边数m=n*(n-1)/2,所以n大致为sqrt(m),所以我们以sqrt(m)为基准将所有的点分为两类:度数<=sqrt(m)的点和度数>sqrt(m)的点。对这两类点分别计算。

对于第一类点,暴力每个点,然后暴力枚举这个点的任意两条边,再判断这两条边的另外两个端点是否连接。枚举一个点的边最多m条,而点的度数是<=sqrt(m)的,所以时间复杂度约为O(m*logm)。

对于第二类点,直接暴力枚举任意三个点,判断三个点是否构成环即可,因为这一类点的个数不会超过sqrt(m)个,所以复杂度约为O(m*logm)。

这个方法可以过统计三元环个数的题目,但是按这个方法没办法按边去统计一个边构成几个三元环(无能为力),因为本题star由两个共享一条边的三元环组成,所以一个三元环是可能贡献给多个star的,即需要重复计算。

附代码:

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int bas = 1e5+1;
const int maxn = 1e5+5;
const int maxm = maxn*2;
struct node
{
	int u, v, next;
} edge[maxm];
int no, head[maxn];
int n, m;
set<LL> hash;
int deg[maxn];
vector<int> more;
void init()
{
	no = 0;
	memset(head, -1, sizeof head);
}
inline void add(int u, int v)
{
	edge[no].u = u; edge[no].v = v;
	edge[no].next = head[u];
	head[u] = no++;
}
void ssort(LL &a, LL &b, LL &c)
{
	LL tmp = a+b+c;
	LL aa = max(max(a, b), c);
	LL cc = min(min(a, b), c);
	a = aa, c = cc, b = tmp-aa-cc;
}
void work()
{
	int x = sqrt(1.0*m);
	LL ans = 0;
	more.clear();
	for(int i = 1; i <= n; ++i)
	{
		if(deg[i] <= x)
		{
			for(int k1 = head[i]; k1+1; k1 = edge[k1].next)
			{
				int b = edge[k1].v;
				if(deg[b] > x || b > i)
				//第二个判断定一个方向,避免重复统计 
				{
					for(int k2 = edge[k1].next; k2+1; k2 = edge[k2].next)
					{
						int c = edge[k2].v;
						if(deg[c] > x || c > i)
						{
							LL bb = b, cc = c;
							if(bb > cc) swap(bb, cc);
							if(hash.find(bb*bas+cc) != hash.end())
							++ans;
						}
					}
				}
			}
		}
		else more.push_back(i);
		//第二类点 
	}
	for(int i = 0; i < more.size(); ++i)
	{
		int a = more[i];
		for(int j = i+1; j < more.size(); ++j)
		{
			int b = more[j];
			if(hash.find(1ll*a*bas+b) == hash.end()) continue;
			for(int k = j+1; k < more.size(); ++k)
			{
				int c = more[k];
				if(hash.find(1ll*a*bas+c) != hash.end()
				&& hash.find(1ll*b*bas+c) != hash.end())
				++ans;
			}
		}
	}
	printf("%lld\n", ans);
}
int main()
{
	int t, u, v;
	for(scanf("%d", &t); t--;)
	{
		init(); hash.clear();
		scanf("%d %d", &n, &m);
		for(int i = 1; i <= m; ++i)
		{
			scanf("%d %d", &u, &v);
			++deg[u], ++deg[v];//抄的时候记得前面清空
			if(u > v) swap(u, v);
			add(u, v); add(v, u);
			hash.insert(1ll*u*bas+v);
		}
		work();
	}	
	return 0;
}


所以最终又搜了搜该题的题解()

抽离出下面求三元环的代码,代码中附注释:

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int bas = 1e5+1;
const int maxn = 1e5+5;
vector<int> G[maxn];
int n, m;
//unordered_set<LL> _hash;
set<LL> _hash;
int deg[maxn], vis[maxn];
int bel[maxn];
void init()
{
	for(int i = 1; i <= n; ++i)
	{
		deg[i] = bel[i] = vis[i] = 0;
		G[i].clear();
	}
}
void work()
{
	int x = sqrt(1.0*m);
	LL ans = 0;
	for(int a = 1; a <= n; ++a)
	{
		vis[a] = 1;
		//扫一遍与a相连的所有点,为下面提供O(1)判两点是否存在连边 
		for(int i = 0; i < G[a].size(); ++i)
		bel[G[a][i]] = a;
		for(int i = 0; i < G[a].size(); ++i)
		{
			int b = G[a][i];
			if(vis[b]) continue;
			if(deg[b] <= x)	
			{
				//如果b度数<=sqrt(m),则枚举b的所有边 
				for(int j = 0; j < G[b].size(); ++j)
				{
					int c = G[b][j];
					if(bel[c] == a) ++ans;
				}
			}
			else
			{
				//如果b度数>sqrt(m),则枚举a的所有边 
				for(int j = 0; j < G[a].size(); ++j)
				{
					int c = G[a][j];
					if(_hash.find(1ll*b*bas+c) != _hash.end())
					++ans;
				}
			}
		}
	}
	//统计后每个三元环的每条边都会被统计一次,所以应该/3 
	printf("%lld\n", ans/3);
}
int main()
{
	int t, u, v;
	while(~scanf("%d %d", &n, &m))
	{
		init(); _hash.clear();
		for(int i = 1; i <= m; ++i)
		{
			scanf("%d %d", &u, &v);
			++deg[u], ++deg[v];
			G[u].push_back(v);
			G[v].push_back(u);
			_hash.insert(1ll*u*bas+v);
			_hash.insert(1ll*v*bas+u);
		}
		work();
	}	
	return 0;
}


但实际题目求三元环时的复杂度和上面的相比哪个更优就不知道了。还是都掌握吧,第二个真心想不太明白。

sq另一个方法也是O(m*logm)的,但是却减少了很多判断。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值