【算法】總體二分初探

  總體二分好喵喵~長得很像決策單調性的分治優化,它可以幫助你不用寫各類樹套主席樹就能很輕易地求出第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); //分治~ 
}
View Code

 

例題時間~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]);
}
View Code

  例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]);
}
View Code

  例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]);
}
View Code

  例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]);
}
View Code

 

持續更新中~

  資料:http://blog.csdn.net/lwt36/article/details/50669972

相關文章
相關標籤/搜索