分塊和離線
這兩堂課應該會講的東西
折半枚舉
有\(n\)個數,\(n\leq 40\),問有多少子集的和為\(x\)
直接枚舉:\(O(2^n)\)燒雞
切一半分別枚舉,把兩邊枚舉出的東西分別排序再用雙指針或二分搜看有多少個:
枚舉:\(O(n\times 2^\frac{n}{2})\)
排序:\(O(2^\frac{n}{2}\times\log2^\frac{n}{2})\)=\(O(n\times 2^\frac{n}{2})\)
找東西:\(O(2^\frac{n}{2}\times\log2^\frac{n}{2})\)=\(O(n\times 2^\frac{n}{2})\)
共\(O(n\times2^\frac{n}{2})\)
code
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a[25],b[25];
vector<ll> aa,bb;
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
ll n,x;
cin>>n>>x;
for(int i=0;i<n/2;i++){
cin>>a[i];
}
for(int i=0;i<(n+1)/2;i++){
cin>>b[i];
}
for(ll c=0;c<(1<<(n/2));c++){
ll ans=0;
for(int i=0;i<n/2;i++){
if(c&(1<<i)){
ans+=a[i];
}
}
aa.push_back(ans);
}
for(ll c=0;c<(1<<((n+1)/2));c++){
ll ans=0;
for(int i=0;i<(n+1)/2;i++){
if(c&(1<<i)){
ans+=b[i];
}
}
bb.push_back(ans);
}
sort(aa.begin(),aa.end());
sort(bb.begin(),bb.end());
ll ans=0;
for(auto u:aa){
ans+=upper_bound(bb.begin(),bb.end(),x-u)-lower_bound(bb.begin(),bb.end(),x-u);
}
cout<<ans<<"\n";
return 0;
}習題
分塊
分塊:把東西切一切,把同一塊的事情一起做
在平面上有\(n\)個格子點\((x_i,y_i),0\leq x_i,y_i\leq 10^6\),\(n\leq 10^6\),找一條哈密頓路徑長度\(\leq 2.5\times 10^9\),兩點距離是他們的曼哈頓距離
把\(y\)每\(k\)個分一塊
每塊照\(x\)排序
\(y\)會動到:\(nk+2000k\)
\(x\)會動到:\(\frac{n}{k}\times n\)
\(y\)
\(x\)
習題
$$\sqrt n維護$$
在這裡的問題通常是把數列切一切,我們會想要切\(\sqrt n\)通常是為了決定\(O(\frac{n}{k}+k)\)的\(k\)
先來看長度為\(n\)的序列,有\(q\)筆操作區間改值,區間查最小值的問題
先來做沒有修改的
我們把序列中每\(k\)個數字分成一塊,每一塊紀錄最小值
查詢
- 完整的塊最多\(O(\frac{n}{k}) \)個
- 剩下暴力最多有\(O(k)\)個
有修改的
打懶標!
改值
- 完整的塊:打懶標,最多\(O(\frac{n}{k}) \)個
- 不完整的塊:推懶標,暴力改值,維護最小值\(O(k)\)
想讓\(\frac{n}{k}+k\)最小
算幾有\(\frac{n}{k}+k\geq 2\sqrt n\),當\(k=\sqrt n\)
code
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e9;
const int blk=450;
int a[200010],val[450],tag[450];
void push(int pos){
if(tag[pos]==maxn)return;
val[pos]=tag[pos];
for(int i=pos*blk;i<(pos+1)*blk;i++){
a[i]=tag[pos];
}
tag[pos]=maxn;
}
void pull(int pos){
val[pos]=maxn;
for(int i=pos*blk;i<(pos+1)*blk;i++){
val[pos]=min(val[pos],a[i]);
}
}
void modify(int l,int r,int x){
int L=l/blk,R=r/blk;
for(int i=L+1;i<R;i++){
tag[i]=x;
}
push(L);
push(R);
if(L==R){
for(int i=l;i<=r;i++)a[i]=x;
}else{
for(int i=l;i<(L+1)*blk;i++)a[i]=x;
for(int i=R*blk;i<=r;i++)a[i]=x;
}
pull(L);
pull(R);
}
int query(int l,int r){
int ans=maxn,L=l/blk,R=r/blk;
for(int i=L+1;i<R;i++){
if(tag[i]!=maxn)ans=min(ans,tag[i]);
else ans=min(ans,val[i]);
}
if(L==R){
for(int i=l;i<=r;i++)ans=min(ans,a[i]);
}else{
for(int i=l;i<(L+1)*blk;i++)ans=min(ans,a[i]);
for(int i=R*blk;i<=r;i++)ans=min(ans,a[i]);
}
return ans;
}有一個長度為\(n\)的序列,有\(q\)筆操作:
- 區間加值
- 給一個數\(y\),問序列中最遠的兩個\(y\)差多遠
\(n\leq 5\times 10^5,q\leq 5\times 10^4\)
把\(k\)個切一塊,需要找最左邊和最右邊的\(y\),用multiset維護每塊有的東西
加值:過整塊的打懶標,剩下暴力維護\(O(\frac{n}{k}+k\log k)\)
詢問:每塊看有沒有\(y\),然後暴力戳\(y\)在哪 \( O( \frac{n}{k}\log k+ k) \)
取\(k=\sqrt n\)然後亂壓有\(O(n\log\sqrt n+q\sqrt n \log\sqrt n)\)
切一切!
code
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll maxn=2e9;
const ll blk=710;
ll a[500010],tag[710],n,q;
multiset<ll> val[710];
void modify(int l,int r,ll x){
int L=l/blk,R=r/blk;
for(int i=L+1;i<R;i++){
tag[i]+=x;
}
if(L==R){
for(int i=l;i<=r;i++){
val[L].erase(val[L].find(a[i]));
a[i]+=x;
val[L].insert(a[i]);
}
}else{
for(int i=l;i<(L+1)*blk;i++){
val[L].erase(val[L].find(a[i]));
a[i]+=x;
val[L].insert(a[i]);
}
for(int i=R*blk;i<=r;i++){
val[R].erase(val[R].find(a[i]));
a[i]+=x;
val[R].insert(a[i]);
}
}
}
int query(ll y){
ll mn=maxn,mx=0;
for(int i=0;i<=n/blk;i++){
if(val[i].find(y-tag[i])!=val[i].end()){
for(int j=i*blk;j<min(n,(i+1)*blk);j++){
if(a[j]==y-tag[i]){
mn=j;
break;
}
}
break;
}
}
for(int i=n/blk;i>=0;i--){
if(val[i].find(y-tag[i])!=val[i].end()){
for(int j=min(n-1,(i+1)*blk-1);j>=i*blk;j--){
if(a[j]==y-tag[i]){
mx=j;
break;
}
}
break;
}
}
return mn<=mx?mx-mn:-1;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>q;
for(int i=0;i<n;i++){
cin>>a[i];
val[i/blk].insert(a[i]);
}
for(int i=0;i<q;i++){
int x;
cin>>x;
if(x==1){
ll l,r,v;
cin>>l>>r>>v;
modify(l-1,r-1,v);
}else{
ll y;
cin>>y;
cout<<query(y)<<"\n";
}
}
return 0;
}習題
操作分塊
有一顆\(n\)點的樹,點被編號從\(1\)到\(n\)
一開始\(1\)是紅色,其他藍色
有\(q\)筆操作會是
1.戳一個藍點把它塗成紅色
2.戳一個點問離它最近的紅點有多近
\(n\leq 10^5,q\leq 10^5\)
把\(k\)個操作分一塊
那每個詢問只要看到塊內的紅點和到塊外的紅點取min
塊內:做lca,可以預處理\(O(1)\)
塊外:每次把\(k\)中改到的點跑多點源bfs
\(O(q\times k+n\times \frac{q}{k})\)
習題
莫隊算法
把區間詢問離線+分塊
通常莫隊算法處理的問題是一些可離線的區間詢問,
而且在知道\([L,R]\)後可以很快(通常\(O(1),O(log\ n)\) )知道\([L+1,R],[L-1,R],[L,R+1],[L,R-1]\)
把詢問畫到平面上
\(L\)
\(R\)
在很多時候你可能會想讓\(l\leq r\)恆成立
假設現在在\((l,r)\)要到\((l',r')\)
如果可能有\(l<r<l'<r'\)就要讓\(r++\)先於\(l++\)
如果可能有\(l'<r'<l<r\)就要讓\(l--\)先於\(r--\)
如果你不想管這麼多的話就先讓區間變大再變小
關於區間要怎麼動
有一個長度為\(n\)的序列\(a\),\(q\)筆詢問\(l_i,r_i\)問
有多少三元組\((i,j,k)\)滿足
- \(l_i\leq i<j<k\leq r_i\)
- \(a_i=a_j=a_k\)
\(n\leq 2\times 10^5,q\leq 2\times 10^5,1\leq a_i\leq 2\times 10^5\)
code
#include<bits/stdc++.h>
#define ll long long
#define F first
#define S second
#define piii pair<pair<int,int>,int>
using namespace std;
const ll blk=450;
ll n,q,nowans,nowl,nowr,ans[200010],cnt[200010],a[200010];
vector<piii> query;
bool cmp(piii a,piii b){
return a.F.F/blk==b.F.F/blk?a.F.S<b.F.S:a.F.F<b.F.F;
}
void add(int pos){
nowans+=cnt[a[pos]]*(cnt[a[pos]]-1)/2;
cnt[a[pos]]++;
}
void sub(int pos){
cnt[a[pos]]--;
nowans-=cnt[a[pos]]*(cnt[a[pos]]-1)/2;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>q;
for(int i=0;i<n;i++){
cin>>a[i];
}
for(int i=0;i<q;i++){
int l,r;
cin>>l>>r;
query.push_back({{l-1,r-1},i});
}
sort(query.begin(),query.end(),cmp);
int nowl=0,nowr=-1;
for(auto u:query){
auto l=u.F.F,r=u.F.S,id=u.S;
while(nowr<r)add(++nowr);
while(nowl>l)add(--nowl);
while(nowr>r)sub(nowr--);
while(nowl<l)sub(nowl++);
ans[id]=nowans;
}
for(int i=0;i<q;i++){
cout<<ans[i]<<"\n";
}
return 0;
}習題
補充
帶修改莫隊
把修改也變成一個維度,然後做和普通莫隊差不多的事,把\(L\)和\(R\)每\(n^{\frac{2}{3}}\)(用算幾壓一壓)切一塊,複雜度會是\(O(n^\frac{5}{3})\)
回滾莫隊
斷了手的莫隊
通常在某些涉及min/max的問題,我們容易從\([L,R]\)推到\([L-1,R],[L,R+1]\)(推一格)但很難推到
\([L+1,R],[L,R-1]\)(縮一格),把詢問再畫到平面上就是只能往兩個方向走,我們試著做和莫隊一樣的事。
當要戳下一個詢問時會有:
1.\(L,R\)在同一塊:讓他自己額外算
2.\(L\)在同一塊:讓\(R\)向右動,然後\(L\)額外算
3.\(L\)在不同塊:重新來過
分case
習題
整體二分搜
多筆詢問全域查詢第\(k\)小的數
二分搜戳\(\leq m\)的數
分別做會\(O(QN\log C)\)
把詢問一起做
把答案區間在\([L,R]\)之間的人戳\(mid\)然後丟到\([L,mid]\)\([mid,R]\)
把二分搜一起做
多筆詢問區間查詢第\(k\)小的數
用BIT維護
其實差不多
改值->刪值再加值
帶修改?
CDQ分治
CDQ是一種思想,我們把\([L,R]\)切成\([L,mid]\)\([mid,R]\)然後想辦法在\([L,mid]\)轉移到\([mid,R]\)時利用一些酷酷的優超性
把點照\(x\)排序,合併時用\(y\)排再用BIT維護\(z\)
三維偏序
分塊離線
By owoovo
分塊離線
- 132