總體二分好喵喵~長得很像決策單調性的分治優化,它可以幫助你不用寫各類樹套主席樹就能很輕易地求出第k小數233333(大霧ios
首先肯定一個決策區間solve(l, r, L, R)表示編號在L~R的操做的數的權值和詢問的答案在l~r這個區間,每次將答案二分,把L~R裏的修改操做按被修改數的權值<=mid和>mid分紅左右兩邊,若是<=mid,就把它下標所在位置在bit裏+1,把L~R裏的查詢操做按bit上查詢區間裏的sum>=k和<k分紅左右兩邊,若是<k,那麼k就要減去bit上查詢區間裏的sum,而後就按丟到左右兩邊的操做分治就行了。數組
應該仍是不難理解的,雖然將操做和詢問分紅左右兩邊修改了原順序,可是會互相影響的操做之間必定是按原順序進行的,由於修改的排名大的數對排名小的數無影響,先處理答案小的,因此處理答案大的時候k已經減去了答案小的時候的貢獻,因而處理答案大的區間的時候實際也不受到答案小的區間的影響,這樣就能作到一層O(N),一共log(N)層,加上bit複雜度log(N),總複雜度O(Nlog^2N)。ide
總體二分的具體流程:優化
void solve(int l, int r, int L, int R) { if(l>r || L>R) return; if(l==r) { for(int i=L;i<=R;i++) if(q[i].ty) ans[q[i].pos]=l; //若是q[i].ty==1是詢問,就把答案丟進去 return; } int mid=(l+r)>>1, cnt1=0, cnt2=0; //二分答案 for(int i=L;i<=R;i++) if(q[i].ty) { int tmp=query(q[i].y)-query(q[i].x-1); //求bit上詢問區間的sum if(tmp>=q[i].k) q1[++cnt1]=q[i]; //<=k就丟左邊 else q[i].k-=tmp, q2[++cnt2]=q[i]; //>k就更新k後丟右邊 } else { if(q[i].x<=mid) add(q[i].pos, q[i].y), q1[++cnt1]=q[i]; //若是被修改數<=mid,就更新bit並丟到左邊 else q2[++cnt2]=q[i]; } for(int i=1;i<=cnt1;i++) if(!q1[i].ty) add(q1[i].pos, -q1[i].y); //若是是修改,刪去在bit上的值 for(int i=1;i<=cnt1;i++) q[L+i-1]=q1[i]; //丟到左邊~ for(int i=1;i<=cnt2;i++) q[L+cnt1+i-1]=q2[i]; //丟到右邊~ solve(l, mid, L, L+cnt1-1); solve(mid+1, r, L+cnt1, R); //分治~ }
例題時間~spa
例1 POJ2104 K-th Number.net
就是個裸的求區間第k小...大模板code
#include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; const int maxn=500010, inf=1e9; struct poi{int x, y, k, pos, ty;}q[maxn], q1[maxn], q2[maxn]; int n, m, x, y, k, cnt, cnt1, cnt2; int tree[maxn], ans[maxn], a[maxn]; inline void read(int &k) { int f=1; k=0; char c=getchar(); while(c<'0' || c>'9') c=='-' && (f=-1), c=getchar(); while(c<='9' && c>='0') k=k*10+c-'0', c=getchar(); k*=f; } inline void add(int x, int delta){for(;x<=n;x+=x&-x) tree[x]+=delta;} inline int query(int x) {int sum=0; for(;x;x-=x&-x) sum+=tree[x]; return sum;} void solve(int l, int r, int L, int R) { if(l>r || L>R) return; if(l==r) { for(int i=L;i<=R;i++) if(q[i].ty) ans[q[i].pos]=l; return; } int mid=(l+r)>>1, cnt1=0, cnt2=0; for(int i=L;i<=R;i++) if(q[i].ty) { int tmp=query(q[i].y)-query(q[i].x-1); if(tmp>=q[i].k) q1[++cnt1]=q[i]; else q[i].k-=tmp, q2[++cnt2]=q[i]; } else { if(q[i].x<=mid) q1[++cnt1]=q[i], add(q[i].pos, q[i].y); else q2[++cnt2]=q[i]; } for(int i=1;i<=cnt1;i++) if(!q1[i].ty) add(q1[i].pos, -q1[i].y); for(int i=1;i<=cnt1;i++) q[L+i-1]=q1[i]; for(int i=1;i<=cnt2;i++) q[L+cnt1+i-1]=q2[i]; solve(l, mid, L, L+cnt1-1); solve(mid+1, r, L+cnt1, R); } int main() { read(n); read(m); for(int i=1;i<=n;i++) read(x), q[++cnt]=(poi){x, 1, 0, i, 0}; for(int i=1;i<=m;i++) read(x), read(y), read(k), q[++cnt]=(poi){x, y, k, i, 1}; solve(-inf, inf, 1, cnt); for(int i=1;i<=m;i++) printf("%d\n", ans[i]); }
例2 bzoj1901: Zju2112 Dynamic Rankingsblog
就多了一個修改,那常數就比BIT套主席樹優越啦,複雜度又同樣,跑的就飛快~get
#include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; const int maxn=500010, inf=1e9; struct poi{int x, y, k, pos, ty;}q[maxn], q1[maxn], q2[maxn]; int n, m, x, y, k, cnt, cnt1, cnt2, tot; int tree[maxn], ans[maxn], a[maxn]; char s[2]; inline void read(int &k) { int f=1; k=0; char c=getchar(); while(c<'0' || c>'9') c=='-' && (f=-1), c=getchar(); while(c<='9' && c>='0') k=k*10+c-'0', c=getchar(); k*=f; } inline void add(int x, int delta){for(;x<=n;x+=x&-x) tree[x]+=delta;} inline int query(int x) {int sum=0; for(;x;x-=x&-x) sum+=tree[x]; return sum;} void solve(int l, int r, int L, int R) { if(l>r || L>R) return; if(l==r) { for(int i=L;i<=R;i++) if(q[i].ty) ans[q[i].pos]=l; return; } int mid=(l+r)>>1, cnt1=0, cnt2=0; for(int i=L;i<=R;i++) if(q[i].ty) { int tmp=query(q[i].y)-query(q[i].x-1); if(tmp>=q[i].k) q1[++cnt1]=q[i]; else q[i].k-=tmp, q2[++cnt2]=q[i]; } else { if(q[i].x<=mid) add(q[i].pos, q[i].y), q1[++cnt1]=q[i]; else q2[++cnt2]=q[i]; } for(int i=1;i<=cnt1;i++) if(!q1[i].ty) add(q1[i].pos, -q1[i].y); for(int i=1;i<=cnt1;i++) q[L+i-1]=q1[i]; for(int i=1;i<=cnt2;i++) q[L+cnt1+i-1]=q2[i]; solve(l, mid, L, L+cnt1-1); solve(mid+1, r, L+cnt1, R); } int main() { read(n); read(m); for(int i=1;i<=n;i++) read(a[i]), q[++cnt]=(poi){a[i], 1, 0, i, 0}; for(int i=1;i<=m;i++) { scanf("%s", s+1); if(s[1]=='Q') read(x), read(y), read(k), q[++cnt]=(poi){x, y, k, ++tot, 1}; else read(x), read(y), q[++cnt]=(poi){a[x], -1, 0, x, 0}, q[++cnt]=(poi){a[x]=y, 1, 0, x, 0}; } solve(-inf, inf, 1, cnt); for(int i=1;i<=tot;i++) printf("%d\n", ans[i]); }
例3 bzoj3110: [Zjoi2013]K大數查詢string
此次要插入一個區間的數,那麼若是插入的數<=mid就用線段樹給那一段區間所有+1就行了...
第k大就n-c+1求第k小最後輸出n-ans+1就行了...
#include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #define int long long using namespace std; const int maxn=50010, inf=2147483647; struct poi{int x, y, k, pos, ty;}q[maxn], q1[maxn], q2[maxn]; struct tjm{int sum, delta;}tree[maxn<<2]; int n, m, ty, x, y, z, cnt, cnt1, cnt2, tot, N; int ans[maxn]; inline void read(int &k) { int f=1; k=0; char c=getchar(); while(c<'0' || c>'9') c=='-' && (f=-1), c=getchar(); while(c<='9' && c>='0') k=k*10+c-'0', c=getchar(); k*=f; } inline void up(int x){tree[x].sum=tree[x<<1].sum+tree[x<<1|1].sum;} inline void down(int x, int l, int r) { int mid=(l+r)>>1; tree[x<<1].delta+=tree[x].delta; tree[x<<1|1].delta+=tree[x].delta; tree[x<<1].sum+=tree[x].delta*(mid-l+1); tree[x<<1|1].sum+=tree[x].delta*(r-mid); tree[x].delta=0; } void update(int x, int l, int r, int cl, int cr, int delta) { if(cl<=l && r<=cr) {tree[x].delta+=delta; tree[x].sum+=delta*(r-l+1); return;} down(x, l, r); int mid=(l+r)>>1; if(cl<=mid) update(x<<1, l, mid, cl, cr, delta); if(cr>mid) update(x<<1|1, mid+1, r, cl, cr, delta); up(x); } int query(int x, int l, int r, int cl, int cr) { if(cl<=l && r<=cr) return tree[x].sum; down(x, l, r); int mid=(l+r)>>1, ans=0; if(cl<=mid) ans=query(x<<1, l, mid, cl, cr); if(cr>mid) ans+=query(x<<1|1, mid+1, r, cl, cr); return ans; } void solve(int l, int r, int L, int R) { if(l>r || L>R) return; if(l==r) { for(int i=L;i<=R;i++) if(q[i].ty) ans[q[i].pos]=n-l+1; return; } int mid=(l+r)>>1, cnt1=0, cnt2=0; for(int i=L;i<=R;i++) if(q[i].ty) { int tmp=query(1, 1, n, q[i].x, q[i].y); if(tmp>=q[i].k) q1[++cnt1]=q[i]; else q[i].k-=tmp, q2[++cnt2]=q[i]; } else { if(q[i].k<=mid) update(1, 1, n, q[i].x, q[i].y, 1), q1[++cnt1]=q[i]; else q2[++cnt2]=q[i]; } for(int i=1;i<=cnt1;i++) if(!q1[i].ty) update(1, 1, n, q1[i].x, q1[i].y, -1); for(int i=1;i<=cnt1;i++) q[L+i-1]=q1[i]; for(int i=1;i<=cnt2;i++) q[L+cnt1+i-1]=q2[i]; solve(l, mid, L, L+cnt1-1); solve(mid+1, r, L+cnt1, R); } #undef int int main() { read(n); read(m); for(int i=1;i<=m;i++) { read(ty); read(x); read(y); read(z); if(ty==1) q[i]=(poi){x, y, n-z+1, i, 0}; else q[i]=(poi){x, y, z, ++tot, 1}; } solve(1, n, 1, m); for(int i=1;i<=tot;i++) printf("%lld\n", ans[i]); }
例4 bzoj2738: 矩陣乘法
就是把例1改爲二維樹狀數組便可...
#include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> using namespace std; const int maxn=500010, inf=1e9; struct poi{int x1, yy1, x2, y2, k, pos;}q[maxn], q1[maxn], q2[maxn]; int n, m, x1, yy1, x2, y2, cnt, x, k, mx; int tree[510][510], ans[maxn]; inline void read(int &k) { int f=1; k=0; char c=getchar(); while(c<'0' || c>'9') c=='-' && (f=-1), c=getchar(); while(c<='9' && c>='0') k=k*10+c-'0', c=getchar(); k*=f; } inline void add2(int x, int y, int delta) {for(;y<=n;y+=y&-y) tree[x][y]+=delta;} inline void add1(int x, int y, int delta) {for(;x<=n;x+=x&-x) add2(x, y, delta);} inline int query2(int x, int y) {int sum=0; for(;y;y-=y&-y) sum+=tree[x][y]; return sum;} inline int query1(int x, int y) {int sum=0; for(;x;x-=x&-x) sum+=query2(x, y); return sum;} inline int query(int x1, int yy1, int x2, int y2) {return query1(x2, y2)-query1(x1-1, y2)-query1(x2, yy1-1)+query1(x1-1, yy1-1);} void solve(int l, int r, int L, int R) { if(l>r || L>R) return; if(l==r) { for(int i=L;i<=R;i++) if(q[i].pos) ans[q[i].pos]=l; return; } int mid=(l+r)>>1, cnt1=0, cnt2=0; for(int i=L;i<=R;i++) if(q[i].pos) { int tmp=query(q[i].x1, q[i].yy1, q[i].x2, q[i].y2); if(tmp>=q[i].k) q1[++cnt1]=q[i]; else q[i].k-=tmp, q2[++cnt2]=q[i]; } else { if(q[i].k<=mid) add1(q[i].x1, q[i].yy1, 1), q1[++cnt1]=q[i]; else q2[++cnt2]=q[i]; } for(int i=1;i<=cnt1;i++) if(!q1[i].pos) add1(q1[i].x1, q1[i].yy1, -1); for(int i=1;i<=cnt1;i++) q[L+i-1]=q1[i]; for(int i=1;i<=cnt2;i++) q[L+cnt1+i-1]=q2[i]; solve(l, mid, L, L+cnt1-1); solve(mid+1, r, L+cnt1, R); } inline int max(int a, int b){return a>b?a:b;} int main() { read(n); read(m); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) read(x), q[++cnt]=(poi){i, j, 0, 0, x, 0}, mx=max(mx, x); for(int i=1;i<=m;i++) { read(x1); read(yy1); read(x2); read(y2); read(k); q[++cnt]=(poi){x1, yy1, x2, y2, k, i}; } solve(0, mx+1, 1, cnt); for(int i=1;i<=m;i++) printf("%d\n", ans[i]); }
持續更新中~