无向图中flyod求最小环

坦白说有向图求最小环我还没去想过,但是拓扑序判断有向图存在最小环应该是要补的。

无向图求最小环,通常的思路有两种,一个是断边

先将ab的边权设置为正无穷,然后跑一遍单源最短路,再恢复原本断掉的那条边,那么二者之和显然就是对于ab而言的最小环的长度了,然后每次更新即可;复杂度应该是O(n^2*E),E是边数。

另一种就是Flyod的应用了,话说Flyod好像用处特别多。(包括用bool型来求闭包的)

与上一种思路其实差不多,但是我们引进了一个中转点k,因为对于一个环来说,它至少是由三个点构成的,所以引进一个点是合理的。

k作为中转点,那么k到a,b的最短路自然就是对应的边权,所以我们只要用一个Flyod求ab的最短路,那么再加上两条与k相连的边的边权,就是对应的环的最小长度了。然后环的最小值也是不断更新的,所以也可以在flyod中进行。

下面是板子。

无向图求最小环(flyod)
void floyd()
{
	res=inf;
	for(int k=1;k<=n;k++)
	{
		for(int i=1;i<k;i++)
			for(int j=i+1;j<k;j++)
				res=min(res,g[i][j]+mp[i][k]+mp[k][j]);
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				g[i][j]=g[j][i]=min(g[i][j],g[i][k]+g[k][j]);
	}
}

//res是最小环答案,g是两点最短路,mp是两点边权!

 注意这里我们取k为三者中的最大值,因为都是先把中转点能更新的所有情况都更新了再去进入下一个循环,但如果三者顺序是乱的,或者说没有加以规定的话,就有可能重复或遗漏更新某种情况。

下面是一道例题。

抽屉原理+最小环

加了一点别的应用,不算板子题。

题目中只有数字,所以要求最小环,还要我们去自己建图,当然,这里所有边权取1.

解析都放在代码里了。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll n;
ll mas[100010];
vector<ll> q[70];//存放对应位为1的数字 
map<ll,ll> mp; 
ll dis[300][300];//62位,每一位最多两个数,所以实际需要建的空间并没有那么大
//e存边权
ll e[300][300]; 
int main()
{ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;++i){
		cin>>mas[i];
		
	}
	for(int i=1;i<=n;++i){
		for(int j=0;j<=62;++j){
			if((mas[i]>>j)&1)
			{
				q[j].push_back(i);//放入下标 
			}
		}
	}
	for(int i=0;i<=62;++i){
		if(q[i].size()>=3){
			cout<<3<<endl;
			return 0;
		} 
	} 
	
	ll p=0;//记录所有点的序号,因为之前记录的是它们的值,但接下来
//我们只用知道它们之间是否有边即可,边权均为1,数字是多少仅对两点间是否有边有影响 
	
	//接下来对每一个数给定一个编号,并去重 
	for(int i=0;i<=62;++i){
		if(q[i].size()==2){
			ll x=q[i][0];
			if(mp[x]==0) mp[x]=++p;
			x=q[i][1];//只可能有之多两个点
			if(mp[x]==0) mp[x]=++p;
		}
	} 
	
	//接下来先初始化 
//	memset(dis,0x3f,sizeof dis);
//	memset(e,0x3f,sizeof e);
//	for(int i=1;i<=p;++i){//取i<=p,这里p是去过重之后的点数 
//		e[i][i]=dis[i][i]=0;//点自身边权为0; 
//	} 
	
	for(int i = 1;i <= p;i++)
    {
        for(int j = 1;j <= p;j++)
        {
            if(i == j) e[i][j] = dis[i][j] = 0;
            else e[i][j] = dis[i][j] = 0x3f3f3f;
        }
    }
	
	//接下来建边
	for(int i=0;i<=62;++i){
		if(q[i].size()==2)
		{//如果该位只有一个数的对应位为1,它也不需要跟别人建边 
			ll x=mp[q[i][0]];//取序号而不是数字本身
			ll y=mp[q[i][1]];
			e[x][y]=e[y][x]=dis[x][y]=dis[y][x]=1;
			//相邻两点的最短路和边权都为1; 
		}
	} 
	ll ans=0x3f3f3f;
	//接下来跑flyod
	for(int k=1;k<=p;++k)//这里k也要取<=p
	{
		for(int i=1;i<k;++i){
			for(int j=i+1;j<k;++j){
				ans=min(ans,dis[i][j]+e[i][k]+e[k][j]);
			}
		}
		for(int i=1;i<=p;++i){//这里 
			for(int j=1;j<=p;++j){//这里也都tm要改成p
				dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
			}
		}
	} 
	if(ans==0x3f3f3f){
		cout<<-1<<endl;
	}
	else cout<<ans<<endl;
	return 0;
}

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值