HDU 6166 && 2017 多校训练:Senior Pan(最短路)



题意:

有一张n个点m条边的有向图,还有一个包含k个点的点集,求出这个点集中任意两点间最短路的最小值


官方题解看不懂。。

如果一条边的两个端点都在这个集合中,就将这条边直接删掉(中间记录下最小值)以后不会再用

之后就可以愉快的SPFA了,len[i]表示到i点的最短路

随便设集合中的一个点x1为起点,令len[x1]=0然后求最短路,跑完之后记录所有的len[xi] (xi>1 && xi∈k)看哪个更小,之后再将集合中的第二个点x2的len[]置为0并加入队列,继续spfa,然后记录所有的len[xi] (xi>2 && xi∈k)看哪个更小,如此操作直到集合中所有点的len[]全为0

不过这样只处理了ai到aj (i<j)的最小值,所以还要初始化len[]倒过来再跑一次spfa,操作和上面一样


例如样例k=3,集合中的点为1,3,5

①删掉所有1,3,5之间的边,ans = min(ans, len(u, v))  (e(u, v)∈k)

②求出1号点到所有点的单源最短路,  然后ans = min(ans, len[3], len[5])

③令len[3]=0,将3号点加入队列继续spfa,ans = min(ans, len[5])

④没必要再加最后5号点了,初始化len数组

⑤求出5号点到所有点的单源最短路,  然后ans = min(ans, len[3], len[1])

⑥令len[3]=0,将3号点加入队列继续spfa,ans = min(ans, len[1])

结束,输出ans


看上去貌似是跑了2*k次spfa其实只跑了2次,因为你并没有清空len[]数组,所以只是相当于手动放缩了k次而已

复杂度O((n+m)log(n+m))

#include<stdio.h>
#include<vector>
#include<string.h>
#include<queue>
#include<algorithm>
using namespace std;
#define LL long long
typedef struct
{
	LL v;
	LL len;
}Road;
Road now;
vector<Road> G[100005];
queue<LL> q;
LL God[100005], vis[100005], sum[100005];
int main(void)
{
	LL T, i, n, m, j, x, y, len, k, bet, cas = 1;
	scanf("%lld", &T);
	while(T--)
	{
		scanf("%lld%lld", &n, &m);
		for(i=1;i<=n;i++)
			G[i].clear();
		for(i=1;i<=m;i++)
		{
			scanf("%lld%lld%lld", &x, &y, &len);
			now.len = len, now.v = y;
			G[x].push_back(now);
		}
		memset(God, 0, sizeof(God));
		scanf("%lld", &k);
		for(i=1;i<=k;i++)
		{
			scanf("%lld", &x);
			God[x] = 1;
		}
		bet = 100000ll*100000+5;
		for(i=1;i<=n;i++)
		{
			if(!God[i])  continue;
			for(j=0;j<G[i].size();j++)
			{
				y = G[i][j].v;
				if(God[y])
				{
					bet = min(bet, G[i][j].len);
					G[i].erase(G[i].begin()+j);
					j--;
				}
			}
		}
		memset(vis, 0, sizeof(vis));
		memset(sum, 62, sizeof(sum));
		for(i=1;i<=n;i++)
		{
			if(God[i])
			{
				vis[i] = 1;
				q.push(i);
				bet = min(bet, sum[i]);
				sum[i] = 0;
				while(q.empty()==0)
				{
					x = q.front();
					q.pop();
					vis[x] = 0;
					for(j=0;j<G[x].size();j++)
					{
						now = G[x][j];
						if(sum[x]+now.len<sum[now.v])
						{
							sum[now.v] = sum[x]+now.len;
							if(vis[now.v]==0)
							{
								vis[now.v] = 1;
								q.push(now.v);
							}
						}
					}
				}
			}
		}
		memset(sum, 62, sizeof(sum));
		for(i=n;i>=1;i--)
		{
			if(God[i])
			{
				vis[i] = 1;
				q.push(i);
				bet = min(bet, sum[i]);
				sum[i] = 0;
				while(q.empty()==0)
				{
					x = q.front();
					q.pop();
					vis[x] = 0;
					for(j=0;j<G[x].size();j++)
					{
						now = G[x][j];
						if(sum[x]+now.len<sum[now.v])
						{
							sum[now.v] = sum[x]+now.len;
							if(vis[now.v]==0)
							{
								vis[now.v] = 1;
								q.push(now.v);
							}
						}
					}
				}
			}
		}
		printf("Case #%lld: %lld\n", cas++, bet);
	}
	return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值