「線段樹優化建圖」CF786B Legacy
阿新 • • 發佈:2021-01-11
區間連邊問題的一個 trick,最近遇到就複習了一下。
之前寫的糞題解,可以來找點樂子看:Link。
簡述
給定 \(n\) 個節點,給定引數 \(s\),有 \(m\) 次操作:
- 給定引數 \(u,v,w\),從 \(u\) 向 \(v\) 連一條權值為 \(w\) 的邊。
- 給定引數 \(u,l,r,w\),從 \(u\) 向 \([l,r]\) 連一條權值為 \(w\) 的邊。
- 給定引數 \(u,l,r,w\),從 \([l,r]\) 向 \(u\) 連一條權值為 \(w\) 的邊。
求 \(s\) 到每個節點的最短路。
\(1\le n,q\le 10^5\),\(1\le w\le 10^9\)。
2S,256MB。
分析
對於區間連邊問題,其解決方案是建立一系列虛點,虛點到實點的權值為 0,一個虛點與某一段連續區間內的實點相連。此時若想對該區間進行區間連邊,直接連向此虛點即可。
為解決區間的拆分問題,可以將虛點建成一個類似線段樹的結構,如下圖所示:
圖中 \(1,2,3,4\) 為實點,\(5,6,7\) 為虛點。
對於連邊操作 \((1, [2,4])\),可以僅令 \(1\) 連向點 \(2\),點 \(6\),從而減少了連邊數。
這種區間的拆分可以放到線段樹上進行。
上面的線段樹的虛邊是自頂向底連線的。對於本題的第三種操作區間向點連邊,再建立一棵自底向頂連邊的線段樹並進行上述過程即可。
關於複雜度,建立虛點後圖中節點個數為 \(n + 2n\log n\)。
每次線上段樹上區間連邊,會新增不多於 \(\log n\) 條邊。總邊數為 \(O(n\log n)\) 級別。
由上,在新圖上跑最短路時間複雜度為 \(O(n\log^2 n)\) 級別。
演算法總時間複雜度 \(O(n\log^2 n)\),空間複雜度 \(O(n\log n)\)。
程式碼
//知識點:線段樹優化建圖 /* By:Luckyblock */ #include <algorithm> #include <cctype> #include <cstdio> #include <cstring> #include <queue> #define LL long long const int kN = 4e5 + 10; const LL kInf = 0x3f3f3f3f3f3f3f3f; //============================================================= int n, q, start, node_num; int e_num, head[kN], v[kN << 4], w[kN << 4], ne[kN << 4]; bool vis[kN]; LL dis[kN]; //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1; for (; isdigit(ch); ch = getchar()) { w = (w << 3) + (w << 1) + (ch ^ '0'); } return f * w; } void Chkmax(int &fir_, int sec_) { if (sec_ > fir_) fir_ = sec_; } void Chkmin(int &fir_, int sec_) { if (sec_ < fir_) fir_ = sec_; } void Add(int u_, int v_, int w_) { v[++ e_num] = v_, w[e_num] = w_; ne[e_num] = head[u_], head[u_] = e_num; } #define ls (lson[now_]) #define rs (rson[now_]) #define mid ((L_+R_)>>1) struct SegmentTree { int root, lson[kN], rson[kN]; void Build(int &now_ ,int L_, int R_, bool type_) { if (L_ == R_) { now_ = L_; return ; } now_ = ++ node_num; Build(ls, L_, mid, type_); Build(rs, mid + 1, R_, type_); if (!type_) Add(now_, ls, 0), Add(now_, rs, 0); if (type_) Add(ls, now_, 0), Add(rs, now_, 0); } void Modify(int now_, int L_, int R_, int l_, int r_, int u_, int w_, bool type_) { if (l_ <= L_ && R_ <= r_) { if (!type_) Add(u_, now_, w_); if (type_) Add(now_, u_, w_); return ; } if (l_ <= mid) Modify(ls, L_, mid, l_, r_, u_, w_, type_); if (r_ > mid) Modify(rs, mid + 1, R_, l_, r_, u_, w_, type_); } } Seg[2]; #undef ls #undef rs #undef mid void Init() { node_num = n; Seg[0].Build(Seg[0].root, 1, n, 0); Seg[1].Build(Seg[1].root, 1, n, 1); } void Spfa(int s_) { //他死了 std::queue <int> q; memset(dis, 0x3f, sizeof(dis)); dis[s_] = 0; q.push(s_); while (!q.empty()) { int u = q.front(); q.pop(); for (int i = head[u]; i; i = ne[i]) { int v_ = v[i], w_ = w[i]; if (dis[u] + w_ < dis[v_]) { dis[v_] = dis[u] + w_; q.push(v_); } } } } #define pr std::pair #define mp std::make_pair void Dijkstra(int s_) { std::priority_queue <pr <LL, int> > q; memset(dis, 63, sizeof (dis)); dis[s_] = 0; q.push(mp(0, s_)); while (! q.empty()) { int u_ = q.top().second; q.pop(); if (vis[u_]) continue; vis[u_] = true; for (int i = head[u_]; i; i = ne[i]) { int v_ = v[i], w_ = w[i]; if (dis[u_] + w_ < dis[v_]) { dis[v_] = dis[u_] + w_; q.push(mp(-dis[v_], v_)); } } } } #undef pr #undef mp //============================================================= int main() { n = read(), q = read(), start = read(); Init(); while (q --) { int opt = read(); if (opt == 1) { int u_ = read(), v_ = read(), w_ = read(); Add(u_, v_, w_); } else { int u_ = read(), l_ = read(), r_ = read(), w_ = read(); int type = (opt == 3); Seg[type].Modify(Seg[type].root, 1, n, l_, r_, u_, w_, type); } } Dijkstra(start); for (int i = 1; i <= n; ++ i) { printf("%lld ", dis[i] < kInf ? dis[i] : -1); } return 0; }