1. 程式人生 > 其它 >CF464E The Classic Problem 題解

CF464E The Classic Problem 題解

Description

洛谷傳送門

Solution

emm……這是我為數不多的黑題之一,所以來寫篇部落格記錄一下。

我們發現邊權過大,只能用高精度來算,但是這樣的複雜度太劣了,無法通過此題。

觀察到邊權只能是 \(2^x\),所以我們可以給它壓成二進位制數,然後跑最短路時單點加。

我們再來考慮一下 \(dijkstra\) 需要哪些操作:

  • 區間加

  • 比大小

先來看區間加,如果第 \(x\) 位為 0,那麼賦值為 1 即可,如果第 \(x\) 位為一,那麼我們需要向上找第一個為 0 的位,然後這一段區間賦值為 0,第一個為 0 的位賦值為 1。

這個查詢操作可以使用線段樹上二分來實現,據說暴力查詢也是可以過的,資料比較水。

那區間賦值為 0 如何實現呢?主席樹似乎不太能支援修改。我們可以建一棵全 0 主席樹,然後修改時直接把這棵全 0 的樹拍上去。

再來看區間比大小,我們發現單純的比區間裡 1 的個數不太行,所以我們給每一棵樹 \(Hash\) 一下,比較 \(Hash\) 值,先把相同的一段找出來,然後比較第一個不同的位的大小。

然後就是程式碼實現了,個人認為非常有助於提升碼力,也讓我對主席樹的理解更深了一步。

Code

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#include <cmath>
#define ull unsigned long long
#define ll long long
#define ls(x) t[x].l
#define rs(x) t[x].r

using namespace std;

inline int read(){
    int x = 0;
    char ch = getchar();
    while(ch < '0' || ch > '9') ch = getchar();
    while(ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x;
}

const ll mod = 1e9 + 7;
const int N = 2e5 + 10;
int n, m, S, T;
struct node{
    int v, w, nxt;
}edge[N << 1];
int head[N], tot;
int inf;
ll fac[N], h[N];
struct Tree{
    ll sum, Hash;//sum:區間 1 的個數   Hash:區間雜湊值
    int l, r, num;//num:區間是否全為 1
    friend bool operator == (Tree a,  Tree b){
        return a.sum == b.sum && a.Hash == b.Hash && a.num == b.num;
    }
}t[N * 40];
int root[N], cnt;//跟,節點編號
int ans[N], path, pre[N];//記錄路徑
bool vis[N];

inline void add(int x, int y, int z){
    edge[++tot] = (node){y, z, head[x]};
    head[x] = tot;
}

inline void pushup(int rt){
    t[rt].num = t[ls(rt)].num + t[rs(rt)].num;
    t[rt].sum = (t[ls(rt)].sum + t[rs(rt)].sum) % mod;
    t[rt].Hash = (t[ls(rt)].Hash + t[rs(rt)].Hash) % mod;
}

inline void build(int &rt, int l, int r, int val){//建樹
    if(!rt) rt = ++cnt;//一定要判斷,不然會 RE。
    if(l == r){
        t[rt].sum = fac[l] * val, t[rt].Hash = h[l] * val, t[rt].num = val;
        return;
    }
    int mid = (l + r) >> 1;
    build(ls(rt), l, mid, val);
    build(rs(rt), mid + 1, r, val);
    pushup(rt);
}

inline int query(int rt, int L, int R, int l, int r){//查詢區間是否全部為 1。
    if(L <= l && r <= R) return t[rt].num;
    int mid = (l + r) >> 1;
    int res = 0;
    if(L <= mid) res += query(ls(rt), L, R, l, mid);
    if(R > mid) res += query(rs(rt), L, R, mid + 1, r);
    return res;
}

inline void update(int &rt, int l, int r, int val){//加點, 0 改為 1。
    t[++cnt] = t[rt];
    rt = cnt;
    if(l == r){
        t[rt].sum = fac[l], t[rt].Hash = h[l], t[rt].num = 1;
        return;
    }
    int mid = (l + r) >> 1;
    if(val <= mid) update(ls(rt), l, mid, val);
    else update(rs(rt), mid + 1, r, val);
    pushup(rt);
}

inline void clear(int &x, int y, int L, int R, int l, int r){//區間賦 0,把 y(全 0 樹) 全部貼到 x 上。
    if(L <= l && r <= R){
        x = y;
        return;
    }
    int mid = (l + r) >> 1, z = ++cnt;
    t[z] = t[x];
    if(L <= mid) clear(ls(z), ls(y), L, R, l, mid);
    if(R > mid) clear(rs(z), rs(y), L, R, mid + 1, r);
    pushup(x = z);
}

inline int search(int rt, int l, int r, int val){//線段樹上二分,向上找第一個不為 1 的位置。
    if(l == r) return l;
    int mid = (l + r) >> 1;
    if(val > mid) return search(rs(rt), mid + 1, r, val);//查詢權值在右子樹。
    if(query(ls(rt), val, mid, l, mid) == mid - val + 1) return search(rs(rt), mid + 1, r, mid + 1);//左子樹全為 1 且 val <= mid,到右子樹查詢。
    return search(ls(rt), l, mid, val);//否則在左子樹查詢。
}

inline bool cmp(int x, int y, int l, int r){
    if(l == r) return t[x].num < t[y].num;
    int mid = (l + r) >> 1;
    if(t[rs(x)] == t[rs(y)]) return cmp(ls(x), ls(y), l, mid);
    return cmp(rs(x), rs(y), mid + 1, r);
}

struct Que{
    int id, rt;
    friend bool operator < (Que a, Que b){
        return cmp(b.rt, a.rt, 0, N - 1);
    }
};

inline void dijkstra(){
    priority_queue <Que> q;
    for(int i = 1; i <= n; ++i)
        root[i] = inf;
    root[S] = root[0];
    q.push((Que){S, root[S]});
    while(!q.empty()){
        int x = q.top().id;
        q.pop();
        if(vis[x]) continue;
        vis[x] = 1;
        for(int i = head[x]; i; i = edge[i].nxt){
            int y = edge[i].v, rt_x = root[x], res = cnt;
            int pos = search(root[x], 0, N - 1, edge[i].w);//找第一個為 0 的位置
            update(rt_x, 0, N - 1, pos);//把這個位置賦值為 1
            if(edge[i].w < pos) clear(rt_x, root[0], edge[i].w, pos - 1, 0, N - 1);//如果邊權小於這個位置,區間賦 0
            if(cmp(rt_x, root[y], 0, N - 1)) pre[y] = x, q.push((Que){y, root[y] = rt_x});//判斷新路徑 < 舊路徑,更新答案,記錄路徑
            else cnt = res;//否則以上操作全部取消
        }
    }
}

int main(){
    n = read(), m = read();
    for(int i = 1; i <= m; ++i){
        int u = read(), v = read(), w = read();
        add(u, v, w), add(v, u, w);
    }
    S = read(), T = read();
    fac[0] = h[0] = 1;
    for(int i = 1; i < N; ++i)
        fac[i] = fac[i - 1] * 2 % mod, h[i] = h[i - 1] * 17 % mod;
    build(root[0], 0, N - 1, 0), build(inf, 0, N - 1, 1);
    dijkstra();
    if(root[T] == inf){
        puts("-1");
        return 0;
    }
    for(int x = T; x != S; x = pre[x]) ans[++path] = x;
    ans[++path] = S;
    printf("%lld\n%d\n", t[root[T]].sum, path);
    for(int i = path; i >= 1; --i)
        printf("%d ", ans[i]);
    return 0;
}

End

本文來自部落格園,作者:xixike,轉載請註明原文連結:https://www.cnblogs.com/xixike/p/15484261.html