HDU-6166 Senior Pan - 2017 Multi-University Training Contest - Team 9(最短路+二进制乱搞)

题意:

给一个<=1e5个点的DAG(有向无环图),再给出k个点,求着k个点之间任意两点的最短距离。

思路:

首先最朴素的想法是枚举两点去求单源单汇最短路,但是k<=1e5不可行,但其实最短路也可以求多源多汇,即跟网络流一样建立一个超级源点和超级汇点,将超级源点与多个源点建立一条权为0的边,将多个汇点与超级汇点建立一条权为0的边。然后求从超源到超汇的最短路,就是多源多汇中的最短路。所以问题就是如何求尽可能少次数次最短路能覆盖掉所有可能呢,所以考虑每个点的编号都是不同,即它们的二进制位至少有一位是不同的,所以只要枚举二进制的位数,然后将k个数分为两个集合去求多源多汇最短路,这样保证了k个数任意两点都会至少有一次处于不同的两个集合中,从而保证答案的正确性。每次还需要将两个集合分别作为源点、汇点去求一次最短路,所以时间复杂度大致为O(m*2*logn);


代码:

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const LL inf = 0x3f3f3f3f3f3f3f3f;
const int maxn = 1e5+5;
const int maxm = maxn*2;
struct node
{
	LL w;
	int v, next;
	bool operator<(const node k) const
	{
		return w > k.w;
	}
} _edge[maxm], edge[maxm];
int _no, _head[maxn], no, head[maxn];
int n, m, K;
LL dis[maxn];
int vis[maxn], chos[maxn];
vector<int> vt1, vt2;
priority_queue<node> q;
inline void init()
{
	_no = 0;
	memset(_head, -1, sizeof _head);
}
inline void _add(int u, int v, int w)
{
	_edge[_no].v = v; _edge[_no].w = 1ll*w;
	_edge[_no].next = _head[u]; _head[u] = _no++;
}
inline void add(int u, int v, int w)
{
	edge[no].v = v; edge[no].w = 1ll*w;
	edge[no].next = head[u]; head[u] = no++;
}
LL DJ(int s, int t)
{
	memset(dis, 0x3f3f, sizeof dis);
	memset(vis, 0, sizeof vis);
	while(!q.empty()) q.pop();
	dis[s] = 0;
	q.push((node){0, s, -1});
	while(!q.empty())
	{
		node top = q.top(); q.pop();
		int u = top.v;
		if(vis[u]) continue;
		vis[u] = 1;
		for(int k = head[u]; k != -1; k = edge[k].next)
		{
			int v = edge[k].v;
			if(dis[v] > dis[u]+edge[k].w)
			{
				dis[v] = dis[u]+edge[k].w;
				q.push((node){dis[v], v, -1});
			}
		}
	}
	return dis[t];
}
void work()
{
	LL ans = inf;
	for(int k = 0; k < 17; ++k)
	{
		vt1.clear(); vt2.clear();
		for(int i = 1; i <= K; ++i)
		if(chos[i]&(1<<k)) vt1.push_back(chos[i]);
		else vt2.push_back(chos[i]);
		if(vt1.empty() || vt2.empty()) continue;
		no = _no;
		for(int i = 0; i < _no; ++i) edge[i] = _edge[i];
		for(int i = 0; i <= n+1; ++i) head[i] = _head[i];
		for(int i = 0; i < vt1.size(); ++i) add(0, vt1[i], 0);
		for(int i = 0; i < vt2.size(); ++i) add(vt2[i], n+1, 0);
		ans = min(ans, DJ(0, n+1));
		no = _no;
		for(int i = 0; i < _no; ++i) edge[i] = _edge[i];
		for(int i = 0; i <= n+1; ++i) head[i] = _head[i];
		for(int i = 0; i < vt1.size(); ++i) add(vt1[i], n+1, 0);
		for(int i = 0; i < vt2.size(); ++i) add(0, vt2[i], 0);
		ans = min(ans, DJ(0, n+1));
	}
	printf("%lld\n", ans);
}
int main()
{
	int t, u, v, w;
	scanf("%d", &t);
	for(int _ = 1; _ <= t; ++_)
	{
		scanf("%d %d", &n, &m); init();
		for(int i = 1; i <= m; ++i)
		{
			scanf("%d %d %d", &u, &v, &w);
			_add(u, v, w);
		}
		scanf("%d", &K);
		for(int i = 1; i <= K; ++i) scanf("%d", &chos[i]);
		printf("Case #%d: ", _); work();
	}
	return 0;
}


继续加油~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值