【引子】
(BZOJ3040)
有一个m边有向图,节点从1~n编号,求从1到n的最短路。
n
≤
1
0
6
,
m
≤
1
0
7
n \leq 10^6,m \leq 10^7
n≤106,m≤107
显然是一个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()
上面的操作不仅很快(都是
O
(
1
)
O(1)
O(1)的),而且还很草,都是随便乱连一通了事。
为了保证这个数据结构的时间复杂度足够优秀,这个操作需要好好设计一下。其实十分简单。
将根的儿子都拆下来,然后两两合并即可(合并的次序结构很像二叉树由下而上)。
不想画图直接搬这个大佬的图片。
据说均摊时间复杂度是
O
(
l
o
g
n
)
O(logn)
O(logn),但是死活找不到证明。
但它快到飞起就不管这个了吧。
不难发现单次操作的时间复杂度为 O ( l o g n ) O(logn) O(logn),而且空间也完全可以接受。
【代码】
#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("");
}