[NOIP模拟][图论]星星

题目描述:
题目大意:给定一个无向图,问图中共边三元环有多少对,点数n<=100000,m<=200000,多组输入输出。
例图:这里写图片描述
样例输入:

2
4 5
1 2
2 3
3 4
4 1
1 3
4 6
1 2
2 3
3 4
4 1
1 3
2 4

样例输出:

1
6

题目分析:
这道题朴素的想法是枚举每条边,然后如果边的两个端点的连变数大于等于3,就再计算这两个点所连的共同的点个数为cnt,它们两两都可构成共边三元环,于是ans+=cnt*(cnt-1)/2。时间复杂度 O(m*n)。
同理如果直接枚举点,时间复杂度 O( n 3 n^3 )。

**正解:**对于每个点,可以先把与它相连的点打上标记,再枚举所有与它相连且连边数比它小的点的边集来求每条边的贡献,可以证明这样的复杂度是O(m m \sqrt{m} )的。实际上避免了重复计算(如|菊|花|图)。

时间复杂度证明:
考虑按每个点的连边数是否大于 m \sqrt{m} 分为重点和轻点。故所有边可分为三种:
1.轻点连轻点:有O(m)条边,但每个轻点的连的边集数最多只有O( m \sqrt{m} )(定义),总复杂度为O(m m \sqrt{m} )。
2.重点连轻点:有O(m)条边,根据做法只枚举轻点的边集,同样只有O( m \sqrt{m} ),总复杂度为O(m m \sqrt{m} )。
3.重点连重点:一个重点最多连O(m)条边,最多有O(2 m \sqrt{m} )个重点(因为算连边数时,每条边被算了两次),且这两种情况是不可能同时存在的,总复杂度也应为O(m m \sqrt{m} )。
PS: 考虑为什么以 m \sqrt{m} 来划分,设划分的连边数为d,则时间复杂度为O( m d + m m d m*d+m*\frac{m}{d} ),均值不等式, m \sqrt{m} 划分最小。
附代码:

#include<iostream>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cstdio>
#include<ctime>
#include<cmath>
#include<cctype>
#include<iomanip>
#include<algorithm>
using namespace std;

const int N=1e5+10;
const int M=2e5+10;
int n,m,tot,t,nxt[M*2],to[M*2],first[N],du[N],vis[N];
long long ans;
struct node{
	int x;
	int y;
}bian[M];

inline int readint()
{
	char ch;int i=0,f=1;
	for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
	if(ch=='-') {ch=getchar();f=-1;}
	for(;ch>='0'&&ch<='9';ch=getchar()) i=(i<<3)+(i<<1)+ch-'0';
	return i*f;
}

inline void create(int x,int y)
{
	tot++;
	nxt[tot]=first[x];
	first[x]=tot;
	to[tot]=y;
}

int main()
{
	//freopen("star.in","r",stdin);
	//freopen("star.out","w",stdout);
	
	int x,y;
	t=readint();
	while(t--)
	{
		tot=0;ans=0;
		memset(first,0,sizeof(first));
		memset(du,0,sizeof(du));
		memset(vis,0,sizeof(vis));
		n=readint();m=readint();
		for(register int i=1;i<=m;i++)
		{
			x=readint();y=readint();
			create(x,y);
			create(y,x);
			du[x]++;du[y]++;
		}
		for(register int i=1;i<=n;i++)
		{
			for(register int e=first[i];e;e=nxt[e]) vis[to[e]]=i;//打标记
			for(register int e=first[i];e;e=nxt[e])
			{
				int v=to[e];
				if(du[v]<=du[i])
				{
					if(du[i]==du[v]&&v<=i) continue;//相等特判,v<=i说明前面已算过
					int cnt=0;
					for(register int p=first[v];p;p=nxt[p])//实际上这里应该用hash来O(1)判,但直接暴力判也过得了
						if(vis[to[p]]==i) cnt++;
					ans+=(long long)cnt*(cnt-1)/2;
				}
			}
		}
		printf("%I64d\n",ans);
	}
	return 0;
}
发布了99 篇原创文章 · 获赞 8 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qianguch/article/details/78397421