【图论】无向图数圈圈

本篇主要讨论圈数较小(k5) 的时候无向图上数圈的方法。

1.k 4

这部分可以做到 n,m105 (点数和边数)。

k=2 : ???不用说。

k=3:我们考虑有方向性的数环避免重复,给每个点定义其属性为 (degi,i)deg 即无向图度数,比较属性时首先比较第一项。这样我们发现这是一个偏序关系。对于每一条边,都定向为小的指向大的。

容易发现每个三元环都是 12,23,131,2,3 代表属性从小到大)的形态,所以枚举每一个点作为 1 ,枚举其出边作为 2 ,枚举 2 的出边作为 3 ,最后判断 1,3 是否连边即可。

判断很简单,每次枚举 1 的时候先给出边的点打上 tag=i ,然后看 tag3 是否等于 i 即可。

看起来很暴力对不对?其实这样是 Θ(mm) 的,下面解释:

我们考虑每次是枚举了一条边,和这条边出点的出度,尝试证明每个点的出度都是 m 的,我们分讨:

  • 如果原来这个点的 degm ,那么定向后出度只会小,所以一定在 m 以内。

  • 如果大于,那么它只会连向 degv 大于它的 degv 。这样的 v 一定满足 degvm ,不超过 m 个。

所以我们证明了每个点的出度是 Θ(m) 的,复杂度得证。

模板题 Luogu P1989

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
pair <int,int> e[N];
vector <int> G[N];
int deg[N],vis[N],n,m;
inline int calc3()
{
	static int vis[N];
	memset(vis,0,sizeof(vis));
	int ret = 0;
	for(int i = 1;i <= n;i++)
	{
		for(auto to : G[i]) vis[to] = i;
		for(auto to : G[i])
			for(auto tw : G[to])
				if(vis[tw] == i)
					ret ++;	
	}
	return ret;
}
int main()
{
	cin>>n>>m;
	for(int i = 1,x,y;i <= m;i++) cin>>x>>y,e[i] = make_pair(x,y),deg[x]++,deg[y]++;
	for(int i = 1;i <= m;i++)
	{
		if(deg[e[i].first] > deg[e[i].second]) G[e[i].second].push_back(e[i].first);
		else if(deg[e[i].first] < deg[e[i].second]) G[e[i].first].push_back(e[i].second);
		else if(e[i].first < e[i].second) G[e[i].first].push_back(e[i].second);
		else G[e[i].second].push_back(e[i].first);
	}
	cout<<calc3();
	return 0;
}

k=4

还是向刚才一样赋一个属性值,这样一个四元环有 3 种形态:

image

我们的基本思路是枚举两条边,将它们交汇在一起用桶统计。

由于一定有一个点被两条出边指向,我们考虑这样的思路,从一个点出发,走一条原图的无向边(新图可正可反),再走一条新图的有向边,走到 v ,将 ans+=potv ,再将 potv++

potv 这个是桶,名字 pot 纪念 xtr Bug_Automation

我们惊奇的发现,这样做正好能将上面的两种情况统计恰好 1 次,但是下面的情况会统计 2 次,怎么办呢?

我们发现统计两次是因为一次从顶上出发,一次从下面出发,所以我们只需要统计时强制规定最后到达的点 v 的属性大于枚举的 x 和中间点 to 就好了,对于前两种情况来说,一定是这样的,对于最后一种情况,顶上和下面一定有一个大于另外一个,就只会统计一次。

时间复杂度 Θ(mm) ,证明同 k=3

模板题 LOJ 191

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
vector <int> e[N],G[N];
int deg[N],n,m,pot[N];
inline bool lrg(int x,int y) // x > y ??
{
	if(deg[x] ^ deg[y]) return deg[x] > deg[y];
	else return x > y;
 } 
int main()
{
	cin>>n>>m;
	for(int i = 1,x,y;i <= m;i++) cin>>x>>y,e[x].push_back(y),e[y].push_back(x),deg[x]++,deg[y]++;
	for(int i = 1;i <= n;i++)
		for(auto to : e[i])
			if(lrg(to,i))
				G[i].push_back(to);
	long long ans = 0;
	for(int i = 1;i <= n;i++)
	{
		for(auto to : e[i])
			for(auto tw : G[to])
				if(lrg(tw,to) && lrg(tw,i)) 
					ans += pot[tw],pot[tw]++;
		for(auto to : e[i])
			for(auto tw : G[to])
				pot[tw] = 0;
	}
	cout<<ans;
	return 0;
}

一道练习题:CF985G Team Players

给定 nm 边无向图,一个三元组如果互相都没有连边,贡献是 Amin+Bmid+Cmax 。求所有三元组贡献和 mod264

1n,m2×105

首先考虑容斥,答案等于 f0f1+f2f3

fi 是选三个点,其中至少有 i 条边的贡献。

f0 好求,每个三元组都可以贡献。

f1 枚举每条边,在除了 u,v 外任选一点都可以,讨论大小即可。

f2 是一个链(两条边并且相接),考虑枚举中间那个点,将出边按照出点大小排序后扫描,假设当前扫到 u,v ,讨论对于已经扫描过的出点 pp[0,min(u,v)1],[min(u,v)+1,max(u,v)1],[max(u,v)+1,n1] 中的哪一个即可。

f3 就是无向图三元环板子,因为计算时会枚举到三个点,所以可以直接计算。

复杂度 Θ(mm)

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
typedef unsigned long long ull;
vector <int> e[N],G[N];
int n,m,deg[N];
ull A,B,C;
inline bool lrg(int x,int y)
{
	if(deg[x] ^ deg[y]) return deg[x] > deg[y];
	return x > y;
}
inline ull calc0()
{
	ull ret = 0;
	for(int i = 0;i < n;i++)
	{
		if(i < n - 1) ret += 1ull * (n - i - 1) * (n - i - 2) / 2 * A * i;
		ret += 1ull * i * (n - 1 - i) * B * i;
		if(i > 0) ret += 1ull * i * (i - 1) / 2 * C * i;
	}
	return ret;
}
inline ull sum(int x) {if(x <= 0) return 0; return 1ull * (x + 1) * x / 2;}
inline ull calc1()
{
	ull ret = 0;
	for(int i = 0;i < n;i++)
		for(auto to : e[i])
		{
			if(to < i) continue;
			ret += (1ull * A * i + 1ull * B * to) * (n - 1 - to) + C * (sum(n - 1) - sum(to));
			ret += (1ull * A * i + 1ull * C * to) * (to - i - 1) + B * (sum(to - 1) - sum(i));
			ret += (1ull * B * i + 1ull * C * to) * i + A * sum(i - 1);
		}
	return ret;
}
inline ull calc2()
{
	ull ret = 0;
	for(int i = 0;i < n;i++)
	{
		ull sig1 = 0,cnt1 = 0,sig2 = 0,cnt2 = 0;
		sort(e[i].begin(),e[i].end());
		for(auto to : e[i])
		{
			if(to < i)
			{
				ret += sig1 + cnt1 * i * C + cnt1 * to * B;
				cnt1++; sig1 += A * to;
			}
			else
			{
				ret += sig1 + cnt1 * i * B + cnt1 * to * C;
				ret += sig2 + cnt2 * i * A + cnt2 * to * C;
				cnt2++; sig2 += B * to;
			}
		}
	}
	return ret;
}
inline int min(int x,int y,int z) {return min(min(x,y),z);}
inline int max(int x,int y,int z) {return max(max(x,y),z);}
inline ull calc3()
{
	static int vis[N];
	memset(vis,-1,sizeof(vis));
	ull ret = 0;
	for(int i = 0;i < n;i++)
	{
		for(auto to : G[i]) vis[to] = i;
		for(auto to : G[i])
			for(auto tw : G[to])
				if(vis[tw] == i)
					ret += min(i,to,tw) * A + (i + to + tw - min(i,to,tw) - max(i,to,tw)) * B + max(i,to,tw) * C;	
	}
	return ret;
}
int main()
{
	cin>>n>>m;
	cin>>A>>B>>C;
	for(int i = 1,x,y;i <= m;i++)
	{
		cin>>x>>y;
		e[x].push_back(y); e[y].push_back(x);
		deg[x]++; deg[y]++;
	}
	for(int i = 0;i < n;i++)
		for(auto to : e[i])
			if(lrg(to,i))
				G[i].push_back(to);
	cout<<calc0() - calc1() + calc2() - calc3();
	return 0;
}
posted @   The_Last_Candy  阅读(110)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示