「2021 集訓隊互測」學姐買瓜
阿新 • • 發佈:2022-03-24
考慮暴力
容易寫出一下程式碼
void solve(int L, int R) { len = 0; for (int i = 1 ; i <= tot ; i++) if (L <= t[i].L && t[i].R <= R)len++, q[len] = t[i]; int ans = 0, last = 0; sort(q + 1, q + 1 + len, cmp); for (int i = 1 ; i <= len ; i++)if (last <= q[i].L)last = q[i].R + 1, ans++; cout << ans << endl; }
提煉一下上面暴力的主要思想:
從左往右,對於一個線段能放就放(滿足 已經放置的 最右 線段端點 <= 當前要放置的 線段 的左端點)
考慮快速處理上述 過程--------------對於每一個 在原序列裡面的線段\(S\) 維護一個字尾 區間 \(P\)
滿足 \(R_S < L_p , 且R_p最小\)
暴力的想法: 每插入一個線段 就對 所有的 線段 看看 這個後繼是否更新
直接做直接寄
考慮優化這個過程
需要支援 :
區間修改後繼
快速跳後繼
於是就可以想到分塊
考慮對原序列分成\(B\)塊
塊內: 維護塊內 每一個塊 往右跳線段 跳出這個塊 所到達的 最靠左 的塊,即相應的點
每一個位置 跳一次線段後 不跳出塊內 所能 到達的 最靠左的位置
塊外: 維護一個整體 修改這個後繼的標記 (即整體取min)
於是就可以寫寫程式碼了(借鑑了某位老哥的程式碼吧)
fa[i] 在i這個塊內跳一次所到達的 位置
to[i] 以i這個位置為起點,跳出這個塊 所能到達的最左位置
tag[i] 區間取min
v[i]: 相應的跳躍次數
inline void ins(int L , int R){ for(int i = 1 ; i < rk[L] ; i++)tag[i] = min(tag[i] , R + 1); if(tag[rk[L]] != INF){ for(int i = RR[rk[L]] ; i >= LL[rk[L]] ; i--){ if(tag[rk[L]] < fa[i]){ fa[i] = to[i] = tag[rk[L]] , v[i] = 1; } } tag[rk[L]] = INF; } if(rk[L] == rk[R + 1]){ for(int i = L ; i >= LL[rk[L]] ; i--){ if(R + 1 < fa[i]){ fa[i] = R + 1; v[i] = v[R + 1] + 1; } } } else{ for(int i = L ; i >= LL[rk[L]] ; i--){ if(R + 1 < fa[i]){ fa[i] = to[i] = R + 1; v[i] = 1; } } } for(int i = RR[rk[L]] ; i >= LL[rk[L]] ; i--){ if(fa[i] <= RR[rk[L]]){ v[i] = v[fa[i]] + 1; to[i] = to[fa[i]]; } } } inline void solve(int L , int R){ int now = L , zz = 0; int minl = INF , minl2 = INF , V; while(now <= R){ minl = min(tag[rk[now]] , fa[now]); minl2 = min(tag[rk[now]] , to[now]); V = v[now]; if(minl > R + 1)break; if(minl2 <= R + 1)zz += V , now = minl2; else zz++ , now = minl; } printf("%d\n" , zz); }