1. 程式人生 > >@loj - [email protected]「CodePlus 2018 4 月賽」最短路

@loj - [email protected]「CodePlus 2018 4 月賽」最短路

目錄


@[email protected]

企鵝國中有 N 座城市,編號從 1 到 N 。

對於任意的兩座城市 i 和 j ,企鵝們可以花費 (i xor j) * C 的時間從城市 i 走到城市 j ,這裡 C 為一個給定的常數。

當然除此之外還有 M 條單向的快捷通道,第 i 條快捷通道從第 Fi 個城市通向第 Ti 個城市,走這條通道需要消耗 Vi 的時間。

現在來自 Penguin Kingdom University 的企鵝豆豆正在考慮從城市 A 前往城市 B 最少需要多少時間?

input
輸入第一行包含三個整數 N, M, C (1 ≤ N ≤ 10^5, 1 ≤ M ≤ 3*10^5, 1 ≤ C ≤ 100),表示企鵝國城市的個數、快捷通道的個數以及題面中提到的給定的常數 C。

接下來的 M 行,每行三個正整數 Fi, Ti, Vi (1 ≤ Fi ≤ N, 1 ≤ Ti ≤N, 1 ≤ Vi ≤ 100),分別表示對應通道的起點城市標號、終點城市標號和通過這條通道需要消耗的時間。

最後一行兩個正整數 A, B,表示企鵝豆豆選擇的起點城市標號和終點城市標號。

output
輸出一行一個整數,表示從城市 A 前往城市 B 需要的最少時間。

simple input
7 2 10
1 3 1
2 4 4
3 6
simple output
34
simple explain
先從 3 走到 2 ,再從 2 通過通道到達 4 ,再從 4 走到 6。

@[email protected]

令人自閉(事實上是因為自己太弱的)的一道題 TAT。

我們有兩類邊:一般邊 與 異或邊。
一種特別暴力的想法就是,按題意給所有點之間連上這兩類邊,跑從 A 出發到 B 的最短路。但是由於異或邊構成的是完全圖,導致這種想法並不可行。

然而我們注意到一般邊的數量是在正常範圍以內。而且我們可不可以利用異或的什麼性質來簡化圖,減少異或邊的數量?

這個時候就可以用位運算最基本的套路:拆位。即把二進位制表示下的每一位分類討論。
對於 i 到 j 的異或邊,它的費用為 i xor j。我們將 i xor j 拆成若干個 2 的冪之和,對應到圖上,即將 i 到 j 的異或邊拆成若干個費用為 2 的冪的邊組成的路徑。

具體怎麼搞呢?我們對於結點 i,只連出 i xor 2^0, i xor 2^1, ... 共 log 條邊。這樣, i 到 j 的最短路徑就對應著原圖中 i 到 j 的異或邊。
而邊數降低為 O(nlog n + M) 條,就可以該怎麼跑最短路就怎麼跑(當然 SPFA 還是該怎麼卡就怎麼卡)。

注意原本的點編號是 1~N,而新圖必須拓展到 0~2^p (2^p > N) 拆位才不會拆出問題來。

@accepted [email protected]

#include<cstdio>
#include<queue>
using namespace std;
const int MAXN = 200000;
const int MAXM = 500000;
const int INF = (1<<30);
struct edge{
    int to, dis;
    edge *nxt;
}edges[MAXM + 20*MAXN + 5], *adj[MAXN + 5], *ecnt=&edges[0];
void addedge(int u, int v, int w) {
    edge *p = (++ecnt);
    p->to = v, p->dis = w;
    p->nxt = adj[u], adj[u] = p;
}
int dist[MAXN + 5], tot; bool vis[MAXN + 5];
struct node{
    int pos, dis;
    node(int _p=0, int _d=0):pos(_p), dis(_d){}
};
bool operator < (node a, node b) {
    return a.dis > b.dis;
}
priority_queue<node>que;
void dijkstra(int S) {
    que.push(node(S, 0));
    for(int i=0;i<=tot;i++)
        dist[i] = INF, vis[i] = false;
    dist[S] = 0;
    while( !que.empty() ) {
        node t = que.top(); que.pop();
        if( vis[t.pos] ) continue;
        vis[t.pos] = true;
        for(edge *p=adj[t.pos];p!=NULL;p=p->nxt) {
            if( t.dis + p->dis < dist[p->to] ) {
                dist[p->to] = t.dis + p->dis;
                que.push(node(p->to, dist[p->to]));
            }
        }
    }
}
int main() {
    int N, M, A, B, C; scanf("%d%d%d", &N, &M, &C);
    for(int i=1;i<=M;i++) {
        int F, T, V;
        scanf("%d%d%d", &F, &T, &V);
        addedge(F, T, V);
    }
    scanf("%d%d", &A, &B);
    for(tot = 1;tot <= N;tot <<= 1);
    for(int i=0;i<tot;i++)
        for(int p=1;p<tot;p<<=1)
            addedge(i, i^p, p*C);
    dijkstra(A); printf("%d\n", dist[B]);
}

@[email protected]

自己在做這道題的時候,發現連續通過兩條異或邊是不優秀的,因為 (i xor j + j xor k) * C >= (i xor j xor j xor k) * C = (i xor k) * C。然後就順著這個方向走了好久……
好像有想到過拆位,然後一瞬間走神就忘記了……
我太弱了 TAT。