LOJ #2048. 「HNOI2016」最小公倍數
阿新 • • 發佈:2018-12-02
題意
有 \(n\) 個點,\(m\) 條邊,每條邊連線 \(u \Leftrightarrow v\) 且權值為 \((a, b)\) 。
共有 \(q\) 次詢問,每次詢問給出 \(u, v, q_a, q_b\) 。
問是否存在一個聯通塊 \(S\) ,使得其中包含 \(u, v\) 且 \(S\) 中的邊 \(e\) 滿足 \(\max_{e \in S} a_e = q_a, \max_{e \in S} b_e = q_b\) 。
\(n, q \le 5 \times 10^4, m \le 10^5\)
題解
對於這種有兩維 \(a, b\) 最大值確定的題,常常可以用分塊來解決問題。
具體來說就是先把 \(a\) 從小到大排序,然後分塊。
我們把每個詢問掛在 \(q_a\) 被第 \(i\) 個塊所有的邊的 \(a_e\) 構成的區間包含的塊中(如果有多個掛在最後一個)。
然後考慮把 \(i\) 之前的所有的邊以及當前區間掛的詢問按 \(b\) 從小到大排序即可。
然後從前往後一次處理每個操作,如果有邊就加入並查集中(並查集維護連通性,以及每個聯通塊 \(S\) 邊權 \(a,b\) 的最大值)。
如果是詢問的話,暴力將當前塊內合法的邊(也就是兩維都不超過當前詢問)加入即可,然後最後記得需要把所有加入的邊撤回。
這個利用可撤銷並查集就行了。(按秩合併)
似乎把塊調成 \(\sqrt {n \log n}\)
總結
對於求兩位偏序需要滿足條件的題,常常可以分塊然後配合可撤銷資料結構來解決。
然後可以調整塊大小優化複雜度?
程式碼
#include <bits/stdc++.h> #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << (x) << endl #define DEBUG(...) fprintf(stderr, __VA_ARGS__) using namespace std; template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; } template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; } inline int read() { int x(0), sgn(1); char ch(getchar()); for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1; for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48); return x * sgn; } void File() { #ifdef zjp_shadow freopen ("P3247.in", "r", stdin); freopen ("P3247.out", "w", stdout); #endif } const int N = 1e5 + 1e3; struct Option { int u, v, a, b, k; inline void Get(int k_ = 0) { u = read(); v = read(); a = read(); b = read(); k = k_; } } E[N], Q[N]; struct Cmpa { inline bool operator () (const Option &lhs,const Option &rhs) const { return lhs.a != rhs.a ? lhs.a < rhs.a : lhs.b < rhs.b; } }; struct Cmpb { inline bool operator () (const Option &lhs,const Option &rhs) const { return lhs.b != rhs.b ? lhs.b < rhs.b : lhs.a < rhs.a; } }; int n, m, blk, ans[N]; namespace Union_Set { Option stk[N]; int top; int fa[N], height[N], maxa[N], maxb[N]; int find(int x) { return fa[x] == x ? x : find(fa[x]); } Option Merge(int u, int v, int a, int b) { u = find(u); v = find(v); if (height[u] > height[v]) swap(u, v); Option tmp = (Option) {u, v, maxa[v], maxb[v], height[v]}; if (height[u] == height[v]) ++ height[v]; fa[u] = v; chkmax(maxa[v], max(a, maxa[u])); chkmax(maxb[v], max(b, maxb[u])); return tmp; } void Retract() { Option cur = stk[top --]; fa[cur.u] = cur.u; maxa[cur.v] = cur.a; maxb[cur.v] = cur.b; height[cur.v] = cur.k; } } Option ask[N]; int len; int id[N], Mina[N], Maxa[N], bel[N], Beg[N], End[N]; int main () { File(); n = read(); m = read(); blk = (int)(sqrt(n * log2(m))) * 0.6; For (i, 1, m) E[i].Get(); sort(E + 1, E + m + 1, Cmpa()); For (i, 1, m) { id[i] = i / blk + 1; if (id[i] != id[i - 1]) { Mina[id[i]] = Maxa[id[i]] = E[i].a; Beg[id[i]] = i; End[id[i - 1]] = i - 1; } else chkmin(Mina[id[i]], E[i].a), chkmax(Maxa[id[i]], E[i].a); } End[id[m]] = m; int q = read(); For (i, 1, q) { Q[i].Get(i); Fordown (j, id[m], 1) if (Mina[j] <= Q[i].a && Q[i].a <= Maxa[j]) { bel[i] = j; break; } } using namespace Union_Set; For (i, 1, m) if (id[i] != id[i - 1]) { len = 0; For (j, 1, q) if (bel[j] == id[i]) ask[++ len] = Q[j]; if (!len) continue ; sort(E + 1, E + i, Cmpb()); sort(ask + 1, ask + len + 1, Cmpb()); For (j, 1, n) fa[j] = j, maxa[j] = maxb[j] = -1; int pa = 1, pb = 1; while (pa < i || pb <= len) { if ((pa < i && E[pa].b <= ask[pb].b) || pb > len) { Merge(E[pa].u, E[pa].v, E[pa].a, E[pa].b); ++ pa; } else { For (j, Beg[id[i]], End[id[i]]) if (E[j].a <= ask[pb].a && E[j].b <= ask[pb].b) stk[++ top] = Merge(E[j].u, E[j].v, E[j].a, E[j].b); int rt = find(ask[pb].u); if (rt == find(ask[pb].v)) ans[ask[pb].k] = maxa[rt] == ask[pb].a && maxb[rt] == ask[pb].b; while (top) Retract(); ++ pb; } } } For (i, 1, q) puts(ans[i] ? "Yes" : "No"); return 0; }