【数据结构】 配对堆&BZOJ 3040

【引子】

(BZOJ3040)
有一个m边有向图,节点从1~n编号,求从1到n的最短路。
n ≤ 1 0 6 , m ≤ 1 0 7 n \leq 10^6,m \leq 10^7 n106,m107


显然是一个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("");
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值