题意:
给一个<=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;
}
继续加油~