题意:n个点,m条有向边,指定k个点,问你其中最近的两点距离为多少

思路:这题的思路很巧妙,如果我们直接枚举两点做最短路那就要做C(k,2)次。

但是我们换个思路,我们把k个点按照二进制每一位的0和1分类logn次,然后做集合最短距离。

因为任意两个不等的数,总有一位不一样,所以每个点都有机会和其他点在不同集合。

那么这样就花了logn次枚举了所有情况。集合最短距离可以指定一个超级源点和超级汇点,然后做两点最短路。

(原文地址:https://www.cnblogs.com/KirinSB/p/10679711.html)
#include<cmath>
#include<set>
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include <iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 100000 + 10;
const ull seed = 131;
const int INF = 0x3f3f3f3f;
const int MOD = 1000000007;
struct Edge
{
    int to, w, next;
} edge[maxn * 2];
struct node
{
    int v, w;
    node(int _v = 0, int _w = 0): v(_v), w(_w) {}
    bool operator < (const node r) const
    {
        return w > r.w;
    }
};
int head[maxn], tot;
void addEdge(int u, int v, int w)
{
    edge[tot].w = w;
    edge[tot].to = v;
    edge[tot].next = head[u];
    head[u] = tot++;
}
int n, m, k;
int dis[maxn], q[maxn];
bool vis[maxn];
int dij(int s, int e)
{
    memset(vis, false, sizeof(vis));
    memset(dis, INF, sizeof(dis));
    priority_queue<node> Q;
    while(!Q.empty()) Q.pop();
    dis[s] = 0;
    Q.push(node(s, 0));
    node tmp;
    while(!Q.empty())
    {
        tmp = Q.top();
        Q.pop();
        int u = tmp.v;
        if(vis[u]) continue;
        vis[u] = true;
        for(int i = head[u]; i != -1; i = edge[i].next)
        {
            int v = edge[i].to;
            if(!vis[v] && dis[v] > dis[u] + edge[i].w)
            {
                dis[v] = dis[u] + edge[i].w;
                Q.push(node(v, dis[v]));
            }
        }
    }
    return dis[e];
}
void init()
{
    memset(head, -1, sizeof(head));
    tot = 0;
}
int a[maxn], b[maxn], w[maxn];
int main()
{
    int T, ca = 1;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= m; i++)
        {
            scanf("%d%d%d", &a[i], &b[i], &w[i]);
        }
        scanf("%d", &k);
        for(int i = 1; i <= k; i++)
        {
            scanf("%d", &q[i]);
        }
        int ans = INF, p = 0;
        while(n >> p)
        {
            init();
            for(int i = 1; i <= m; i++)
                addEdge(a[i], b[i], w[i]);
            for(int i = 1; i <= k; i++)
                if((q[i] >> p) & 1)
                    addEdge(0, q[i], 0);
                else
                    addEdge(q[i], n + 1, 0);
            ans = min(ans, dij(0, n + 1));
            init();
            for(int i = 1; i <= m; i++)
                addEdge(a[i], b[i], w[i]);
            for(int i = 1; i <= k; i++)
            {
                if((q[i] >> p) & 1)
                    addEdge(q[i], 0, 0);
                else
                    addEdge(n + 1, q[i], 0);
            }
            ans = min(ans, dij(n + 1, 0));
            p++;
        }
        printf("Case #%d: %d\n", ca++, ans);
    }
    return 0;
}
 
12-30 03:07