@(2019牛客暑期多校訓練營(第八場)E_Explorer)
題意:
題目類似:CF366D,Gym101652T
本題給你\(n(100000)\)個點\(m(10000)\)條邊,每無向邊允許通過編號在\([Li,Ri](1\le Li\le Ri\le 1e9)\)內的人,問從\(1\)到\(n\)能通過多少個人。
分析:
賽中艹了30多發暴力無濟於事。。。
因爲以前做過一道數據範圍1000的原題,當時做法好像是離散化後枚舉區間暴力跑\(dfs\)查詢或者帶着區間跑暴力\(BFS\)最後檢查一遍。。。
流下了菜雞的淚水,主要是思維受限了,這很致命。
聽說這題可以LCT寫,目前只會可撤銷並查集+線段樹的寫法。。
- 上面講的也很清楚了,把所有的權值離散化一下,然後建一個以權值爲關鍵字的線段樹
- 這裏提一下,這個線段樹和上一場牛客的E題一模一樣,是左閉右開線段樹,就是每個葉子節點代表的是一段權值區間。這個線段樹的解釋:here
- 然後線段樹上每個節點存的是覆蓋當前點的權值區間的所有邊。
- 區間更新完之後,把整顆樹遍歷一遍得出答案。
- 對於從根到葉子節點這段的權值區間中包含的邊,用一個並查集維護連通性。
- 回溯時要撤銷上面幾條邊的影響。記錄一下合併上一條邊的兩個根,然後還原他們的\(fa[],rnk[]\)就行。
- 用一個棧記錄加入的邊即可,後進先出的結構正適合本題的撤銷操作。還有,可撤銷並查集不能路徑壓縮,必須按秩合併。
Code
const int MXN = 3e3 + 7;
const int MXE = 2e6 + 7;
int n, m;
vector<int> sum[MXN<<2], vs;
int fa[MXN], rnk[MXN], top;
struct lh {
int fi, se, u, v;
}stak[MXN];
struct lp {
int u, v, l ,r;
}cw[MXN];
void update(int L, int R, int v, int l ,int r, int rt) {
if(L <= l && r <= R) {
sum[rt].eb(v);
return;
}
int mid = (l + r) >> 1;
if(L > mid) update(L, R, v, mid + 1, r, rt<<1|1);
else if(R <= mid) update(L, R, v, l, mid, rt<<1);
else {
update(L, mid, v, l, mid, rt<<1), update(mid + 1, R, v, mid + 1, r, rt<<1|1);
}
}
int Fi(int x) {
return fa[x] == x? x: Fi(fa[x]);
}
int pa, pb, ans;
void build(int l, int r, int rt) {
if(l == r) {
// debug(l, r, Fi(3), Fi(5))
for(auto x: sum[rt]) {
pa = Fi(cw[x].u), pb = Fi(cw[x].v);
if(pa == pb) continue;
// debug(l, r, cw[x].u, cw[x].v)
if(rnk[pa] > rnk[pb]) swap(pa, pb);
fa[pa] = pb;
rnk[pb] += rnk[pa];
stak[++ top] = {rt, x, pa, pb};
}
if(Fi(1) == Fi(n)) ans += vs[l+1] - vs[l];
while(top && stak[top].fi == rt) {//撤銷
int x = stak[top].se;
if(rnk[stak[top].u] > rnk[stak[top].v]) {
fa[stak[top].v] = stak[top].v;
rnk[stak[top].u] -= rnk[stak[top].v];
}else {
fa[stak[top].u] = stak[top].u;
rnk[stak[top].v] -= rnk[stak[top].u];
}
-- top;
}
// debug(l, r, Fi(3), Fi(5))
return;
}
int mid = (l + r) >> 1;
// debug(l, r, mid, rt)
for(auto x: sum[rt]) {
pa = Fi(cw[x].u), pb = Fi(cw[x].v);
if(pa == pb) continue;
// debug(pa, pb)
if(rnk[pa] > rnk[pb]) swap(pa, pb);
fa[pa] = pb;
rnk[pb] += rnk[pa];
stak[++ top] = {rt, x, pa, pb};
}
build(l, mid, rt << 1), build(mid + 1, r, rt << 1 | 1);
while(top && stak[top].fi == rt) {//撤銷
int x = stak[top].se;
if(rnk[stak[top].u] > rnk[stak[top].v]) {
fa[stak[top].v] = stak[top].v;
rnk[stak[top].u] -= rnk[stak[top].v];
}else {
fa[stak[top].u] = stak[top].u;
rnk[stak[top].v] -= rnk[stak[top].u];
}
-- top;
}
}
int main() {
#ifndef ONLINE_JUDGE
freopen("/home/cwolf9/CLionProjects/ccc/in.txt", "r", stdin);
// freopen("/home/cwolf9/CLionProjects/ccc/out.txt", "w", stdout);
#endif
n = read(), m = read();
for(int i = 1; i <= n; ++i) fa[i] = i, rnk[i] = 1;
vs.eb(0);
for(int i = 1; i <= m; ++i) {
cw[i].u =read(), cw[i].v = read();cw[i].l = read(), cw[i].r = read();
vs.eb(cw[i].l), vs.eb(cw[i].r + 1);
}
my_unique(vs);
// for(auto x: vs) printf("%d ", x); printf("\n");
for(int i = 1; i <= m; ++i) {
update(lower_bound(all(vs), cw[i].l) - vs.begin(), upper_bound(all(vs), cw[i].r) - vs.begin() - 1, i, 1, vs.size() - 1, 1);
// debug(lower_bound(all(vs), cw[i].l) - vs.begin(), upper_bound(all(vs), cw[i].r) - vs.begin() - 1, cw[i].u, cw[i].v);
}
build(1, vs.size() - 1, 1);
printf("%d\n", ans);
return 0;
}
兩道待補練習題:CF 813F 可撤銷並查集+分治,BZOJ 3237 CDQ分治+帶撤銷並查集
複習帶權並查集和可持久化並查集。