1. 程式人生 > 實用技巧 >C# 在程式碼裡呼叫其他Webapi

C# 在程式碼裡呼叫其他Webapi

好久沒寫過東西了...懶狗一條。

參考了:OI Wiki - 整體二分

為了理解與實現上的方便,本文中的實現均採用vector,空間複雜度為$O(nlogn)$;而IO選手更應該採用另一種實現方法(雙指標+重排序),空間複雜度為$O(n)$。不過本質上思想是一樣的,只是實現上的偷懶與否。


整體二分不同於資料結構:資料結構往往是需要線上修改、線上查詢(可能需要離線預處理,如離散化);而整體二分則是充分利用離線的優勢,將所有的詢問同時進行處理。

而其適用的範圍是,每個查詢的答案均具有單調性。這樣說起來有點抽象,不妨直接認為是“查詢區間內從小到大的第$k$個數”:如果我們二分出的中點$mid$大於答案$ans$,那麼小於等於$mid$的數的數量$num$肯定大於等於$k$;如果$mid\leq ans$,那麼$num\leq k$。

這說的恰恰是主席樹和動態主席樹的經典模板。

1. 不帶修改,查詢區間從小到大第$k$個數POJ 2104,K-th Number)

先將陣列$a$離散化得到陣列$b$後,考慮這樣處理所有的查詢:

一開始,所有的詢問$\{l,r,k,id\}$都被扔到了一個vector中。此時答案的範圍是$1\thicksim m$($m$是離散化後不同$b_i$的數量)。

我們二分出一箇中點$mid=(1+m)/2$,然後考慮將這些詢問分到兩個vector $vl,vr$中,其中$vl$中詢問的答案小於等於$mid$、$vr$中詢問的答案大於$mid$。如何檢驗一個詢問的答案是否小於等於$mid$呢?我們可以利用樹狀陣列,先把所有$1\leq b_i\leq mid$的$i$全部$+1$。那麼對於某個具體的詢問$\{l,r,k,id\}$,其答案小於等於

$mid$的條件就是$[l,r]$的區間和(即區間中小於等於$mid$的數的個數)$cnt$大於等於$k$。

這樣一來,對於所有的詢問,我們可以分別各進行一次BIT query將其放入$vl$或$vr$中。需要注意的是,對於所有放入$vr$的詢問,我們需要將它們的$k$各減去分別的$cnt$。這是因為在之後的處理中,我們僅需要計算$mid+1\thicksim m$的數有多少個出現在其包含的區間中。

以上是第一次對半劃分的情況。對於一般的情況,答案區間為$[l,r]$,$mid=(l+r)/2$,我們將$l\leq b_i\leq mid$的$i$全部$+1$。其餘部分均是一樣的。

需要記得在每次對半劃分後需要將樹狀陣列清空。這裡的清空不能用memset,而是需要進行新增的逆操作、在原來$+1$的位置$-1$。

分析一下複雜度。由於我們是對答案區間進行劃分,所以劃分的深度為$logn$;而由於相同深度的vector的並就是查詢的全集,所以每個查詢都會被劃分$logn$次,而每次劃分的複雜度是$(n+q)logn$(BIT query),所以整體的複雜度為$O((n+q)(logn)^2)$。

關鍵部分的程式碼:

struct Query
{
    int l,r,k,id;
}; //詢問的定義

vector<int> vpos[N]; //vpos[i]包含了,所有離散化後為i的數 在a中的下標

void solve(int l,int r,vector<Query> vq)
{
    if(l==r) //l=r則vq中的答案確定
    {
        for(int i=0;i<vq.size();i++)
            ans[vq[i].id]=l;
        return;
    }
    
    int mid=(l+r)>>1; //二分中點
    for(int i=l;i<=mid;i++) //將所有l<=b_i<=r的i全部+1
        for(int j=0;j<vpos[i].size();j++)
            bit.add(vpos[i][j],1);
    
    vector<Query> vl,vr;
    for(int i=0;i<vq.size();i++)
    {
        Query q=vq[i];
        int cnt=bit.query(q.r)-bit.query(q.l-1); //cnt為[l,r]區間中l~mid出現的次數
        if(cnt>=q.k)
            vl.push_back(q);
        else
            q.k-=cnt,vr.push_back(Query(q));
    }
    
    for(int i=l;i<=mid;i++) //清空樹狀陣列
        for(int j=0;j<vpos[i].size();j++)
            bit.add(vpos[i][j],-1);
    
    solve(l,mid,vl);
    solve(mid+1,r,vr);
}

完整程式碼:

#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;

struct Query
{
    int l,r,k,id;
    Query(int a=0,int b=0,int c=0,int d=0)
    {
        l=a,r=b,k=c,id=d;
    }
};

const int N=100005;

int n,m;
int a[N];

int rev[N];
vector<int> v;

int ans[N];
vector<int> vpos[N];

struct BIT
{
    int t[N];
    
    int lowbit(int x)
    {
        return x&(-x);
    }
    
    inline void add(int k,int x)
    {
        for(int i=k;i<=n;i+=lowbit(i))
            t[i]+=x;
    }
    
    inline int query(int k)
    {
        int ans=0;
        for(int i=k;i;i-=lowbit(i))
            ans+=t[i];
        return ans;
    }
}bit;

void solve(int l,int r,vector<Query> vq)
{
    if(l==r)
    {
        for(int i=0;i<vq.size();i++)
            ans[vq[i].id]=l;
        return;
    }
    
    int mid=(l+r)>>1;
    for(int i=l;i<=mid;i++)
        for(int j=0;j<vpos[i].size();j++)
            bit.add(vpos[i][j],1);
    
    vector<Query> vl,vr;
    for(int i=0;i<vq.size();i++)
    {
        Query q=vq[i];
        int cnt=bit.query(q.r)-bit.query(q.l-1);
        if(cnt>=q.k)
            vl.push_back(q);
        else
            q.k-=cnt,vr.push_back(Query(q));
    }
    
    for(int i=l;i<=mid;i++)
        for(int j=0;j<vpos[i].size();j++)
            bit.add(vpos[i][j],-1);
    
    solve(l,mid,vl);
    solve(mid+1,r,vr);
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]),v.push_back(a[i]);
    
    sort(v.begin(),v.end());
    v.resize(unique(v.begin(),v.end())-v.begin());
    
    for(int i=1;i<=n;i++)
    {
        int pos=lower_bound(v.begin(),v.end(),a[i])-v.begin()+1;
        vpos[pos].push_back(i);
        rev[pos]=a[i];
    }
    
    vector<Query> vq;
    for(int i=1;i<=m;i++)
    {
        int l,r,k;
        scanf("%d%d%d",&l,&r,&k);
        vq.push_back(Query(l,r,k,i));
    }
    
    solve(1,v.size(),vq);
    
    for(int i=1;i<=m;i++)
        printf("%d\n",rev[ans[i]]);
    return 0;
}
View Code

2. 帶修改,查詢區間從小到大第$k$個數ZOJ 2212,Dynamic Rankings)

這道題看起來就比上一題複雜了許多——畢竟用資料結構實現從主席樹進化到了樹套樹。

不過,用整體二分仍然可以比較輕鬆地解決;雖然離實現還需要進行不少的分析。

首先考慮能否像上一題一樣,將查詢與操作分離,從而使得vector中只存放查詢?答案是否定的。這是因為帶修改引入了“時間”這個因素,而我們在進行$vq$到$vl,vr$的劃分時會將時間打亂,因此在每次BIT query時 無法得知是否存在操作對於當前詢問已經“過期”,故無法計算。

那麼可以嘗試將查詢與操作一同放入vector中同時處理。其中,給陣列$a$賦初值視為一次插入操作,進行一次單點修改視為一次刪除操作後接一次插入操作。

看起來仍然無法處理“時間”因素?其實還有一些潛在的性質可以利用。由於在一開始,所有的指令均是按照時間順序插入vector的,而且在每一層的處理都是按照vector的順序進行,所以劃分出的$vl,vr$中的指令分別按時間單調增(只不過時間不再連續)。

我們按照vector的順序依次處理$vq$中的指令。當答案區間為$[l,r]$時,二分中點為$mid=(l+r)/2$。對於所有的詢問仍然和之前一樣,在樹狀陣列上查詢$[l,r]$的區間和$cnt$,並將$cnt$與$k$比較以放入$vl$或$vr$;而對於所有操作,僅在被修改值(不論是插入還是刪除)小於等於$mid$時才在樹狀陣列上修改、並放入$vl$,否則放入$vr$。

考慮一下上述做法的正確性。對於所有$l\thicksim mid$的數的貢獻,假如其未被刪除那麼就和不帶修改的情況一樣,會在賦初值的那一段指令中在樹狀陣列上$+1$;假如其會被修改,那麼在被修改之前能夠正確產生貢獻,而在被修改時則在樹狀陣列上$-1$,從而對之後的詢問不產生貢獻。這樣一起處理 與 將查詢和操作分離的區別,就在於能夠保證按照時間處理。(如果將兩者分離的話,雖然理論上也可行,但需要根據當前詢問的時間 維護不同值的操作的指標,實現起來相當繁瑣)

關鍵部分的程式碼:

//在表示操作時,x=下標,y=離散化值,k=插入1/刪除-1,op=0,id=0
//在表示查詢時,x=l,y=r,k=k,op=1,id=id
struct Opt
{
    int x,y,k,op,id;
    Opt(int a=0,int b=0,int c=0,int d=0,int e=0)
    {
        x=a,y=b,k=c,op=d,id=e;
    }
};

void solve(int l,int r,vector<Opt> opt)
{
    if(l==r) //當l=r時,opt中的所有查詢操作結果均為l
    {
        for(Opt cur: opt)
            if(cur.op==1)
                ans[cur.id]=l;
        return;
    }
    
    int mid=(l+r)>>1;
    vector<Opt> vl,vr;
    for(Opt cur: opt)
        if(cur.op==1) //op=1,是查詢指令
        {
            int cnt=bit.query(cur.y)-bit.query(cur.x-1);
            if(cnt>=cur.k)
                vl.emplace_back(cur);
            else
                cur.k-=cnt,vr.emplace_back(cur);
        }
        else //op=0,是操作指令
            if(cur.y<=mid) //當操作值小於等於mid,在樹狀陣列上進行修改、並放入vl
                bit.add(cur.x,cur.k),vl.emplace_back(cur);
            else //否則直接放入vr
                vr.emplace_back(cur);
    
    for(Opt cur: opt) //樹狀陣列清空
        if(cur.op==0 && cur.y<=mid)
            bit.add(cur.x,-cur.k);
    
    solve(l,mid,vl);
    solve(mid+1,r,vr);
}

完整程式碼:

#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;

struct Opt
{
    int x,y,k,op,id;
    Opt(int a=0,int b=0,int c=0,int d=0,int e=0)
    {
        x=a,y=b,k=c,op=d,id=e;
    }
};

const int N=200005;

int n,m;
int qcnt,ans[N];

struct BIT
{
    int t[N];
    
    int lowbit(int x)
    {
        return x&(-x);
    }
    
    inline void add(int k,int x)
    {
        for(int i=k;i<=n;i+=lowbit(i))
            t[i]+=x;
    }
    
    inline int query(int k)
    {
        int ans=0;
        for(int i=k;i;i-=lowbit(i))
            ans+=t[i];
        return ans;
    }
}bit;

void solve(int l,int r,vector<Opt> opt)
{
    if(l==r)
    {
        for(Opt cur: opt)
            if(cur.op==1)
                ans[cur.id]=l;
        return;
    }
    
    int mid=(l+r)>>1;
    vector<Opt> vl,vr;
    for(Opt cur: opt)
        if(cur.op==1)
        {
            int cnt=bit.query(cur.y)-bit.query(cur.x-1);
            if(cnt>=cur.k)
                vl.emplace_back(cur);
            else
                cur.k-=cnt,vr.emplace_back(cur);
        }
        else
            if(cur.y<=mid)
                bit.add(cur.x,cur.k),vl.emplace_back(cur);
            else
                vr.emplace_back(cur);
    
    for(Opt cur: opt)
        if(cur.op==0 && cur.y<=mid)
            bit.add(cur.x,-cur.k);
    
    solve(l,mid,vl);
    solve(mid+1,r,vr);
}

int a[N];
int op[N],x[N],y[N],k[N];

vector<int> v;

inline int getpos(int x)
{
    return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        qcnt=0;
        v.clear();
        scanf("%d%d",&n,&m);
        
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]),v.emplace_back(a[i]);
        for(int i=1;i<=m;i++)
        {
            char buf[5];
            scanf("%s",buf);
            
            scanf("%d%d",&x[i],&y[i]);
            if(buf[0]=='Q')
                op[i]=1,scanf("%d",&k[i]);
            else
                op[i]=0,v.emplace_back(y[i]);
        }
        
        sort(v.begin(),v.end());
        v.resize(unique(v.begin(),v.end())-v.begin());
        
        vector<Opt> opt;
        for(int i=1;i<=n;i++)
            opt.emplace_back(Opt(i,getpos(a[i]),1,0,0));
        for(int i=1;i<=m;i++)
            if(op[i]==1)
                opt.emplace_back(Opt(x[i],y[i],k[i],1,++qcnt));
            else
            {
                opt.emplace_back(Opt(x[i],getpos(a[x[i]]),-1,0,0));
                a[x[i]]=y[i];
                opt.emplace_back(Opt(x[i],getpos(y[i]),1,0,0));
            }
        
        solve(1,v.size(),opt);
        
        for(int i=1;i<=qcnt;i++)
            printf("%d\n",v[ans[i]-1]);
    }
    return 0;
}
View Code

暫時感覺帶修改查詢的整體二分已經比較牛了,如果遇到標誌性的玩法再繼續補充。


整點例題。

洛谷P3242 (接水果,HNOI2015)

直接看題目還是有點麻煩的,需要進行轉化。轉化有兩種思路:

一種是將水果的路徑拎起來,在上面找子路徑。也就是說,盤子的端點要分別在$lca(x,y)$到$x,y$的路徑上、或者均在同一條路徑上。由於任意點到根的一段路徑無法簡單提出,所以需要樹剖後獲得一段連續的dfs序。然後只能依次列舉盤子兩端所在的dfs序段,從而提取出所有的盤子路徑,然後線段樹上二分,抽象上來說是計算矩形內的點數。時間複雜度爆炸(感覺是$O(n(logn)^3)$),細節也很多。

另一種是將盤子的路徑拎起來,看能夠成為多少水果路徑的子路徑。考慮固定盤子端點$u,v$時,水果路徑的端點可能出現在哪裡。記dfs整棵樹時,進入$x$點的時間戳為$in[x]$、離開的時間戳為$out[x]$。那麼一共只有三種情況:

1. 盤子的路徑不是一條鏈,那麼水果的端點必然分別存在於兩個盤子端點的子樹中

2. 盤子的路徑是一條鏈,那麼其中一個水果路徑端點必然在較深盤子端點的子樹中,另一個水果路徑端點有兩種情況

可以看出,該盤子能夠接住的水果的兩端點dfs序應在所確定的矩形區域內。我們不妨規定端點為$x,y$的水果的座標為$(in[x],in[y])$(其中規定順序為$in[x]$小於$in[y]$),那麼一個盤子能夠覆蓋的區域為x座標為較小dfs序範圍、y座標為較大dfs序範圍 的矩形。

於是,對於每個水果(詢問),就是要求能夠覆蓋該點的矩形中,值從小到大的第$k$個。與序列的區間相比,“覆蓋”是稍微複雜一點的,不過可以考慮使用掃描線來解決。由於x/y座標都是dfs序,所以範圍都在$[1,n]$之內,那麼我們考慮將每個矩形拆成上邊界和下邊界兩個線段,如下圖所示:

然後考慮按照y座標從小到大的順序開始掃描。一開始掃描到$y=y_1$,那麼區間$[x_1,x_3]$均被覆蓋了一遍,即是樹狀陣列在$x_1$處$+1$、在$x_3$處$-1$;然後掃描到$y=y_2$,將區間$[x_2,x_4]$覆蓋一遍;然後掃描到$y=y_3+1$(在$y=y_3$上的點仍然被覆蓋),將區間$[x_1,x_3]$的覆蓋刪去,即是樹狀陣列在$x_1$處$-1$、在$x_3$處$+1$;然後掃描到$y=y_4+1$,將區間$[x_1,x_3]$的覆蓋刪去。

我們在排序矩形的線段時,也將所有的水果的點一起按照y座標排序。當遇到一個水果時,BIT query水果的x座標就能得到其被覆蓋的次數。其餘部分就是帶修改整體二分的板子了。

整體時間複雜度$O(n(logn)^2)$。

#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;

struct penta
{
    int l,r,y,op,k;
    penta(int a=0,int b=0,int c=0,int d=0,int e=0)
    {
        l=a,r=b,y=c,op=d,k=e;
    }
};

const int N=100005;

int n,p,q;
vector<int> v[N];

struct BIT
{
    int t[N];
    
    int lowbit(int x)
    {
        return x&(-x);
    }
    
    void add(int k,int x)
    {
        for(int i=k;i<=n;i+=lowbit(i))
            t[i]+=x;
    }
    
    int query(int k)
    {
        int ans=0;
        for(int i=k;i;i-=lowbit(i))
            ans+=t[i];
        return ans;
    }
}bit;

int ans[N];
vector<penta> opt[N];

inline bool cmp(const penta &X,const penta &Y)
{
    if(X.y!=Y.y)
        return X.y<Y.y;
    return X.op<Y.op;
}

//for querys: x=l, y=y, id=r, op=2, k=k
//for operations: l=l, r=r, y=y, op=1
void solve(int l,int r,vector<penta> vq)
{
    if(l==r)
    {
        for(penta cur: vq)
            ans[cur.r]=l;
        return;
    }
    
    int mid=(l+r)>>1;
    vector<penta> vl,vr;
    
    vector<penta> ord;
    for(int i=l;i<=mid;i++)
        for(penta cur: opt[i])
            ord.emplace_back(cur);
    for(penta cur: vq)
        ord.emplace_back(cur);
    
    sort(ord.begin(),ord.end(),cmp);
    
    for(penta cur: ord)
        if(cur.op==2)
        {
            int cnt=bit.query(cur.l);
            if(cnt>=cur.k)
                vl.emplace_back(cur);
            else
                cur.k-=cnt,vr.emplace_back(cur);
        }
        else
        {
            bit.add(cur.l,cur.op);
            bit.add(cur.r+1,-cur.op);
        }
    
    for(penta cur: ord)
        if(cur.op<2)
        {
            bit.add(cur.l,-cur.op);
            bit.add(cur.r+1,cur.op);
        }
    
    solve(l,mid,vl);
    solve(mid+1,r,vr);
}

int to[N][16];
int tot,dep[N],in[N],out[N];

inline void dfs(int x,int fa)
{
    to[x][0]=fa;
    in[x]=++tot;
    dep[x]=dep[fa]+1;
    
    for(int y: v[x])
        if(y!=fa)
            dfs(y,x);
    out[x]=tot;
}

inline int lca(int x,int y)
{
    if(dep[x]<dep[y])
        swap(x,y);
    for(int i=15;i>=0;i--)
        if(dep[to[x][i]]>=dep[y])
            x=to[x][i];
    if(x==y)
        return x;
    
    for(int i=15;i>=0;i--)
        if(dep[to[x][i]]!=dep[to[y][i]])
            x=to[x][i],y=to[y][i];
    return to[x][0];
}

int a[N],b[N],c[N];

int main()
{
    scanf("%d%d%d",&n,&p,&q);
    for(int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        v[x].emplace_back(y);
        v[y].emplace_back(x);
    }
    
    dfs(1,0);
    for(int i=1;i<16;i++)
        for(int j=1;j<=n;j++)
            to[j][i]=to[to[j][i-1]][i-1];
    
    vector<int> vec;
    for(int i=1;i<=p;i++)
    {
        scanf("%d%d%d",&a[i],&b[i],&c[i]);
        vec.emplace_back(c[i]);
    }
    
    sort(vec.begin(),vec.end());
    vec.resize(unique(vec.begin(),vec.end())-vec.begin());
    
    for(int i=1;i<=p;i++)
    {
        int x=a[i],y=b[i],w=c[i];
        int pos=lower_bound(vec.begin(),vec.end(),w)-vec.begin()+1;
        
        if(in[x]>in[y])
            swap(x,y);
        if(lca(x,y)==x)
        {
            int z=y;
            for(int j=15;j>=0;j--)
                if(dep[to[z][j]]>dep[x])
                    z=to[z][j];
            
            opt[pos].emplace_back(penta(1,in[z]-1,in[y],1));
            opt[pos].emplace_back(penta(1,in[z]-1,out[y]+1,-1));
            if(out[z]<n)
            {
                opt[pos].emplace_back(penta(in[y],out[y],out[z]+1,1));
                opt[pos].emplace_back(penta(in[y],out[y],n+1,-1));
            }
        }
        else
        {
            opt[pos].emplace_back(penta(in[x],out[x],in[y],1));
            opt[pos].emplace_back(penta(in[x],out[x],out[y]+1,-1));
        }
    }
    
    vector<penta> vq;
    for(int i=1;i<=q;i++)
    {
        int x,y,k;
        scanf("%d%d%d",&x,&y,&k);
        
        if(in[x]>in[y])
            swap(x,y);
        vq.emplace_back(penta(in[x],i,in[y],2,k));
    }
    
    solve(1,vec.size(),vq);
    
    for(int i=1;i<=q;i++)
        printf("%d\n",vec[ans[i]-1]);
    return 0;
}
View Code

(完)