P5304 [GXOI/GZOI2019]旅行者

Problem

給一個\(n\)個點,\(m\)條邊的有向圖。
\(k\)個特殊點\(K_1,K_2,\cdot,K_k\)
\(k\)個特殊點中兩兩最短路的最小值。
數據範圍:
c++

Solution

Thinking 1

Floyd...好像暴力都沒得打。/kkspa

Thinking 2

DAG!能夠拓撲搞。
然而並不會!code

Thinking 3

思路真的很妙。
建超級起點\(S\)和超級終點\(T\)
考慮枚舉每一個二進制位:
枚舉關鍵點:blog

  • 若當前關鍵點的當前二進制位爲1,則加邊\(S \to K_i\),邊權爲0.
  • 不然,加邊\(K_i \to T\),邊權爲0.
    對於當前來講,最小的最短路爲\(S\to T\)的最短路,易證。
    而後對於每一個二進制位還要反着作一遍。由於是有向邊,\(x \to y\)的最短路與\(y \to x\)的最短路可能不一樣。

這樣作爲何是對的呢?
其實就是須要證實一個問題:對於每一個特殊點對\(K_i,K_j\),他們都至少一次被分到了不一樣的集合。
那很顯然,由於特殊點互不相同,那麼至少有一個二進制位不一樣,就會被分到不一樣的集合。it

# include <bits/stdc++.h>
using namespace std;
const int N = 100005,inf = 1e9 + 7;
int Test;
int n,m,k;
struct edge
{
    int v,w; 
    edge() {}
    edge(int _v,int _w) : v(_v),w(_w) {}
};
vector <edge> g[N];
int K[N];
int dis[N]; bool vis[N];
int S,T;
void dij(void)
{
    priority_queue <pair<int,int>,vector <pair<int,int> > ,greater<pair<int,int> > > q;
    for(int i = 1; i <= n + 2; i++) dis[i] = inf,vis[i] = 0;
    dis[S] = 0;
    q.push(make_pair(0,S));
    while(!q.empty())
    {
        int x = q.top().second;q.pop();
        if(vis[x]) continue;
        vis[x] = 1;
        for(int i = 0; i < (int)g[x].size(); i++)
        {
            int v = g[x][i].v;
            if(dis[v] > dis[x] + g[x][i].w)
            {
                dis[v] = dis[x] + g[x][i].w;
                q.push(make_pair(dis[v],v));
            }
        }
    }
    // for(int i = 1; i <= n + 2; i++) printf("dis[%d] = %d\n",i,dis[i]);
    return;
}
int main(void)
{
    scanf("%d",&Test);
    while(Test--)
    {
        scanf("%d%d%d",&n,&m,&k);
        for(int i = 1; i <= n; i++) g[i].clear();
        for(int i = 1; i <= m; i++)
        {
            int x,y,z; scanf("%d%d%d",&x,&y,&z);
            g[x].push_back(edge(y,z));
        }
        for(int i = 1; i <= k; i++)
        {
            scanf("%d",&K[i]);
        }
        S = n + 1, T = n + 2;
        int ans = inf;
        for(int i = 0; i == 0 || (1 << (i - 1)) <= n; i++)
        {
            g[S].clear(); vector <int> S1;
            for(int j = 1; j <= k; j++)
            {   
                if(K[j] >> i & 1)
                {
                    g[S].push_back(edge(K[j],0));
                }
                else g[K[j]].push_back(edge(T,0)),S1.push_back(K[j]);
            }
            dij();
            ans = min(ans,dis[T]);
            for(int j = 0; j < (int)S1.size(); j++)
            {
                // printf("del = %d\n",S1[j]);
                g[S1[j]].pop_back();
            }
            S1.clear();
            g[S].clear();
            for(int j = 1; j <= k; j++)
            {
                if(!(K[j] >> i & 1))
                {
                    g[S].push_back(edge(K[j],0));
                }
                else g[K[j]].push_back(edge(T,0)),S1.push_back(K[j]);
            }
            dij();
            ans = min(ans,dis[T]);
            for(int j = 0; j < (int)S1.size(); j++)
            {
                g[S1[j]].pop_back();
            }
            S1.clear();
        }
        printf("%d\n",ans);
    }
    return 0;
}
相關文章
相關標籤/搜索