图论(最短路专题): P1552 牛的旅行 题解

01.题目理解

给你\(n(n<150)\)个点 \(m\)条无向带权边构成的多个联通块 问加一条边后连通块内直径的最小值

直径:连通块内距离最远2点的最短路

02.思路分析

step1:标记连通块

考虑染色法

题目中有多个连通块 我们可以从每个点开始跑一遍\(dfs\)

将搜索中经过的所有点和出发点染成相同颜色

即将连通块内所有的点染成相同颜色

void dfs(int u,int c)
{
    col[u]=c;
    for(edge ed:e[u])
    {
        int v=ed.v;
        if(col[v]) continue;
        dfs(v,c);
    }
}

step2:找到连通块中任意2点的距离并计算以每个点为端点形成直径长度

这是一步预处理操作 避免第三步时间达到\(O(n^4)\)量级

我们发现\(n\)最大只有\(150\)

显然可以直接打\(O(n^3)floyd\)寻找每两个点间的距离

当然也可以打\(dijkstra\) 复杂度\(O(n^2\) \(log\) \(n)\)

//O(n^2 log n)
    for(int i=1;i<=n;++i) 
    {  
        dijkstra(i);
        for(int j=1;j<=n;++j)
            if(inf-dis[j]>0.2)
                max_dis[i]=max(dis[j],max_dis[i]);
    }

step3:暴力连边 计算最小距离

现在我们有了每个点的\(max\)_\(dis\)

显然对于我们可以对于两个不在同一连通块的点进行连边

连边后长度为\(max\)_ \(dis[i]+max\)_ \(dis[j]+len(i,j)\)

计算该值的最小值即可

坑点:直径在连边后可能仍然为最长边

原算法中将其误认为2连通块相连后的长度

所以要将\(ans\)和原来直径作比较 修改后即可\(AC\)

03.代码实现

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=200,inf=0x3f3f3f3f3f3f3f3f;
int x[N],y[N],col[N];
double max_dis[N];
struct edge
{
    int v;
    double w;
};
vector<edge> e[N];
struct node
{
	double dis;
    int u;

	bool operator>(const node& a)const
	{
		return dis>a.dis;
	}
};
double dis[N];
int vis[N];
priority_queue<node,vector<node>,greater<node> >q;
void dijkstra(int s)
{
	for(int i=0;i<200;++i)
        dis[i]=inf;
    for(int i=0;i<200;++i)
        vis[i]=0; 
	dis[s]=0;
	q.push({0,s});
	while(!q.empty())
	{
		int u=q.top().u;
		q.pop();
		if(vis[u])continue;
		vis[u]=1;
		for(auto ed:e[u])
		{
			double w=ed.w;
      int v=ed.v;
			if(dis[v]>dis[u]+w)
			{
				dis[v]=dis[u]+w;
				q.push({dis[v],v});
			}
		}
	}
}
vector<int> farm[N]; 
double len(int a,int b)
{
    int delx=x[a]-x[b];
    int dely=y[a]-y[b];
    return sqrt(delx*delx+dely*dely);
}
void dfs(int u,int c)
{
    col[u]=c;
    for(edge ed:e[u])
    {
        int v=ed.v;
        if(col[v]) continue;
        dfs(v,c);
    }
}
signed main()
{
//	freopen("x.in","r",stdin);
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int n;
    cin>>n;
    for(int i=1;i<=n;++i)
        cin>>x[i]>>y[i];
     for(int i=1;i<=n;++i)
     {
        string str;
        cin>>str;

        str="0"+str;
        for(int j=1;j<str.size();++j)
        	if(str[j]=='1')
            	e[i].push_back({j,len(i,j)});//存边 
     }
     //todo1:寻找连通块 
    int c=1; 
    for(int i=1;i<=n;++i)
        if(col[i]==0)
            dfs(i,c),++c;
    for(int i=1;i<=n;++i)
        farm[col[i]].push_back(i);
    //todo2:计算每个点的最远距离点
    for(int i=1;i<=n;++i) 
    {  
        dijkstra(i);
        for(int j=1;j<=n;++j)
            if(inf-dis[j]>0.2)
                max_dis[i]=max(dis[j],max_dis[i]);
    }
    //todo3:暴力连边 计算最小距离 
    double ans=LONG_LONG_MAX,ret=-1;
    vector<int> i2;
    for(int i=1;i<c;++i)
    for(int j=i+1;j<c;++j)
    for(int ik:farm[i])
    for(int jk:farm[j])
    {
        if(ans>max_dis[ik]+max_dis[jk]+len(ik,jk))  
            i2.push_back(i),i2.push_back(j),ans=max_dis[ik]+max_dis[jk]+len(ik,jk);
    }


    for(int i:i2)
        for(int ik:farm[i])
            ans=max(ans,max_dis[ik]);
	printf("%.6lf",ans);
}
posted @ 2025-02-14 15:43  SamXia  阅读(28)  评论(0)    收藏  举报