【引子】
(BZOJ3040)
有一個m邊有向圖,節點從1~n編號,求從1到n的最短路。
顯然是一個dijkstra+堆優化的板題。
然而一般的堆會被卡空間。
所以我們需要一個更高效的堆。
網上找了半天,可選的數據結構好像只有斐波那契堆和配對堆。然而前者好難寫啊
ε=(´ο`*)))唉。
接下來介紹一下配對堆。
(附:如果想偷懶不學這個東西,可使用平板電視考試時候爆了別怪我qwq,或者可以學學這位大佬***水過這題)
【配對堆】
配對堆和普通的堆不大一樣,它是一顆多叉樹,但依舊滿足堆頂的元素是一個最值。
對於一個配對堆,有以下幾個操作(以小根堆爲例):
·q.link()
該操作實現將兩個堆合併爲一個堆(A<B)。
只需將A和B直接連接即可。
int link(int x, int y)
{
if(val[x] > val[y]) swap(x, y);
int p = edges.pop();//獲取一個空位置,這麼做的目的是爲了儘量減少空間消耗
to[p] = y, nxt[p] = fir[x], fir[x] = p, fa[y] = x; return x;//用鏈式前向星維護堆的樹結構
}
·q.push()
將新加入的節點看做一個堆,然後同q.link()
void push(int x, ll y)
{
int p = nodes.pop(); //同edges
pos[p] = x, cur[x] = p, val[p] = y, //記錄位置,方便之後的q.update()
rt = rt ? link(p, rt) : p, ++siz;
}
·q.update()
該操作將修改一個節點的值(注意爲了保持堆的結構,只能改爲更小的值)。
發現若將一個點入堆很多次,空間開銷將大大增加。所以我們需要這個操作來避免重複入堆。
首先將將要修改的點從堆中拆出來,修改之後,再執行q.link()即可。
void update(int x, ll y)
{
int p = cur[x];
if(p && p != rt)
fa[p] = 0;
val[p] = y;
if(rt != p)
rt = link(rt, p);
}
·q.top()
直接讀取根的信息即可,代碼略。
·q.pop()
上面的操作不僅很快(都是的),而且還很草,都是隨便亂連一通了事。
爲了保證這個數據結構的時間複雜度足夠優秀,這個操作需要好好設計一下。其實十分簡單。
將根的兒子都拆下來,然後兩兩合併即可(合併的次序結構很像二叉樹由下而上)。
不想畫圖直接搬這個大佬的圖片。
據說均攤時間複雜度是,但是死活找不到證明。
但它快到飛起就不管這個了吧。
不難發現單次操作的時間複雜度爲,而且空間也完全可以接受。
【代碼】
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mn = 1000005, mm = 10000005;
struct edge{
int to, w;
}e[mm];
struct stk{
int a[mn * 20], top, cnt;
void push(int x) {a[++top] = x;}
int pop() {return top ? a[top--] : ++cnt;}
};
int fir[mn], nxt[mm], cnt;
ll dis[mn]; bool vis[mn];
struct pair_heap{
stk nodes, edges;
int que[mn * 20]; ll val[mn];
int to[mn], fir[mn], nxt[mn], fa[mn], pos[mn], cur[mn], rt, siz;
int link(int x, int y)
{
if(val[x] > val[y]) swap(x, y);
int p = edges.pop();
to[p] = y, nxt[p] = fir[x], fir[x] = p, fa[y] = x; return x;
}
void push(int x, ll y)
{
int p = nodes.pop();
pos[p] = x, cur[x] = p, val[p] = y, rt = rt ? link(p, rt) : p, ++siz;
}
void update(int x, ll y)
{
int p = cur[x];
if(p && p != rt)
fa[p] = 0;
val[p] = y;
if(rt != p)
rt = link(rt, p);
}
int top() {return pos[rt];}
void pop()
{
int h = 0, t = 0;
for(int i = fir[rt]; i; i = nxt[i])
{
edges.push(i);
if(fa[to[i]] == rt) que[++t] = to[i], fa[to[i]] = 0;
}
nodes.push(rt), cur[pos[rt]] = 0, pos[rt] = fir[rt] = fa[rt] = 0, rt = 0, --siz;
while(h < t)
{
++h;
if(h == t) {rt = que[h]; return;}
int a = que[h], b = que[++h]; que[++t] = link(a, b);
}
}
bool empty() {return siz == 0;}
}q;
inline int getint()
{
int ret = 0, flg = 1; char c;
while((c = getchar()) < '0' || c > '9')
if(c == '-') flg = -1;
while(c >= '0' && c <= '9')
ret = ret * 10 + c - '0', c = getchar();
return ret * flg;
}
inline void addedge(int a, int b, int c) {e[++cnt] = (edge) {b, c}, nxt[cnt] = fir[a], fir[a] = cnt;}
inline void dijkstra(int s)
{
memset(dis, 0x3f, sizeof dis), dis[s] = 0, q.push(s, 0);
while(!q.empty())
{
int s = q.top();
q.pop();
if(vis[s]) continue;
vis[s] = 1;
for(int i = fir[s]; i; i = nxt[i])
{
int t = e[i].to;
if(dis[t] > dis[s] + e[i].w)
{
if(dis[t] == 0x3f3f3f3f3f3f3f3fll) q.push(t, dis[s] + e[i].w);
else q.update(t, dis[s] + e[i].w);
dis[t] = dis[s] + e[i].w;
}
}
}
}
int main()
{
int n = getint(), m = getint(), s = getint();, T = getint(), rxa = getint(), rxc = getint(), rya = getint(), ryc = getint(), rp = getint();
int x = 0, y = 0, c = 0, a, b;
/*for(int i = 1; i <= T; i++)
{
x = (1ll * x * rxa + rxc) % rp,
y = (1ll * y * rya + ryc) % rp,
a = min(x % n + 1, y % n + 1),
b = max(y % n + 1, y % n + 1), addedge(a, b, 1e8-100*a);
}*/ //數據超水把隨機邊扔掉都能過(誰叫它邊權太大了呢ε=(´ο`*)))唉),
//不註釋直接TLE(大常數選手的無奈)
for(int i = 1; i <= m; i++)
a = getint(), b = getint(), c = getint(), addedge(a, b, c);
dijkstra(s);
for(int i = 1; i <= n; i++)
printf("%lld ", dis[i]);
puts("");
}