CF464E The Classic Problem 題解
阿新 • • 發佈:2021-10-30
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