1. 程式人生 > >ACM樹和資料結構

ACM樹和資料結構

ACM樹和資料結構

樹這個結構是真的神奇,很多演算法和複雜一點的資料結構,都是以樹為基礎的,
因為樹結構的可以再很快的時間(logn)去解決很多問題。
比如 去做一個dfs搜尋,實際上就是一個狀態空間上的搜尋樹。
然後就是線段樹、平衡樹、動態樹、Trie樹(字首樹)。都用來解決一些特殊的問題。

下面從頭開是講樹

0、樹的結構和性質

大部分資料結構書上都說了,總結一下,就是1對n,
實現上:三種實現方式。

  • 父指標
    fa[N];
  • 兒子指標(用於M叉樹)
    son[N][M];
  • 兒子兄弟指標(其實和圖一樣,用於一般的樹和森林);

1、基本元素的求法

  • 直徑

    題目poj1849

    #include<iostream>
    #include<cstring>
    #include<vector>
    #define rep(i,l,r) for(int i=l;i<r;i++)
    #define Rep(i,l,r) for(int i=l;i<=r;i++)
    #define rrep(i,l,r) for(int i=r;i>=l;i--)
    #define lc rt<<1
    #define lson l,m,rt<<1
    #define rc rt<<1|1
    #define rson m+1,r,rt<<1|1
    #define mp make_pair
    #define pb push_back
    #define all(x) x.begin(),x.end()
    using namespace std;
    typedef long long ll;
    typedef pair<int,int> pii;
    const int N=1e5+10;
    vector<pii> G[N];
    int d,n,s,maxDep,lp,rp,ans;
    bool vis[N];
    int dep[N];
    void dfs(int u){
        vis[u]=true;
        if(dep[u]>maxDep){
            maxDep=dep[u];
            lp=u;
        }
        int sz=G[u].size();
        int v;
        rep(i,0,sz){
            v=G[u][i].second;
            if(!vis[v]){
                dep[v]=dep[u]+G[u][i].first;
                dfs(v);
            }
        }
    }
    int main()
    {
        //freopen("in.txt","r",stdin);
        //freopen("out.txt","w",stdout);
        ios::sync_with_stdio(false);
        while(cin>>n>>s){
            int x,y,v;
            ans=0;
            Rep(i,1,n)G[i].clear();
            rep(i,0,n-1){
                cin>>x>>y>>v;
                ans+=v;
                G[x].pb(mp(v,y));
                G[y].pb(mp(v,x));
            }
            ans*=2;
            //樹的直徑dfs兩遍
            memset(vis,false,sizeof vis);
            maxDep=-1,lp=-1;
            dep[1]=0;
            dfs(1);
            memset(vis,false,sizeof vis);
            int tp=lp;
            maxDep=-1,lp=-1;
            dep[tp]=0;
            dfs(tp);
            //dep[lp]為直徑
            ans-=dep[lp];
            cout<<ans<<endl;
        }
        return 0;
    }
    
    
  • 重心

    題目poj1655

    int dp[N];
    void dfs(int u){
        vis[u]=true;dp[u]=1;int sz=G[u].size();
        rep(i,0,sz){
            int v=G[u][i];
            int res=0;
            if(!vis[v]){
                dfs(v);
                dp[u]+=dp[v];
                res=max(res,dp[v])
            }
            res=max(res,n-dp[u]);
            if(res<mmin){
                mmin=res;
                center=u;
            }
        }
    }
    
  • 點分治

    #TODO
    

2、並查集

  • 路徑壓縮版、權值合併
struct UFSet{
    static const int N=1e3+10;
    int fa[N],rnk[N];
    void init(){memset(fa,-1,sizeof fa);memset(rnk,0,sizeof rnk);}
    int Find(int x){return fa[x]==-1?x:fa[x]=Find(fa[x]);}
    //void init(){for(int i=1;i<N;i++)fa[i]=i;memset(rnk,0,sizeof rnk);}
    //int Find(int x){return fa[x]==x?x:fa[x]=Find(fa[x]);}
    int Union(int x,int y){
        int r1=Find(x),r2=Find(y);
        if(r1==r2)return 0;
        if(rnk[r1]>rnk[r2]) fa[r2]=r1;
        else{
            fa[r1]=r2;
            if(rnk[r1]==rnk[r2])rnk[r2]++;
        }
        return 1;
    }
};

【uva11987】帶刪除的並查集
題意:初始有N個集合,分別為 1 ,2 ,3 …n。有三種操件
1 p q 合併元素p和q的集合
2 p q 把p元素移到q集合中
3 p 輸出p元素集合的個數及全部元素的和。

因為只是維護集合關係,而不是圖的連通關係,所以可以直接建立一個新的點

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

typedef long long LL;
const int N=10*100100;
int n,m,tot,id[N],fa[N],cnt[N];
LL sum[N];

int findfa(int x)
{
    if(fa[x]==x) return x;
    return findfa(fa[x]);
}

int main()
{
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        for(int i=1;i<=n;i++) id[i]=i,fa[i]=i,cnt[i]=1,sum[i]=i;
        tot=n;
        for(int i=1;i<=m;i++)
        {
            int tmp,x,y,xx,yy;
            scanf("%d",&tmp);
            if(tmp==1)
            {
                scanf("%d%d",&x,&y);
                xx=findfa(id[x]);yy=findfa(id[y]);
                if(xx==yy) continue;//debug
                fa[xx]=yy;
                sum[yy]+=sum[xx];
                cnt[yy]+=cnt[xx];
                sum[xx]=0;cnt[xx]=0;
            }
            if(tmp==2) 
            {
                scanf("%d%d",&x,&y);
                xx=findfa(id[x]);yy=findfa(id[y]);
                tot++;
                id[x]=tot;
                fa[tot]=yy;
                sum[yy]+=(LL)x;
                cnt[yy]++;
                sum[xx]-=(LL)x;
                cnt[xx]--;
            }
            if(tmp==3)
            {
                scanf("%d",&x);
                xx=findfa(id[x]);
                printf("%d %lld\n",cnt[xx],sum[xx]);
            }
            // for(int j=1;j<=n;j++)
            // {
                // printf("%d fa = %d cnt = %d sum = %d\n",j,fa[id[j]],cnt[id[j]],sum[id[j]]);
            // }
            // printf("\n");
        }
    }
    
    return 0;
}

3、DFS序

4、樹狀陣列維護

struct BIT{
    ll c[N];
    int len;
    int lbt(int x){return x&(-x);}
    void init(int n){len=n;memset(c,0,sizeof c);}
    void upd(int x,int v){
        while(x<=n){
            c[x]+=v;
            x+=lbt(x);
        }
    }
    ll query(int x){
        ll ret=0;
        while(x>0){
            ret+=c[x];
            x-=lbt(x);
        }
        return ret;
    }
};

5、樹鏈剖分(重鏈剖分)

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

int n,M,P;
char op[10];
int l,r,v;
//樹鏈剖分
const int MAXN = 50010;
struct Edge
{
    int to,next;
}edge[MAXN*2];
int head[MAXN],tot;
int top[MAXN];//top[v] 表示v所在的重鏈的頂端節點
int faz[MAXN];//父親節點
int dep[MAXN];//深度
int num[MAXN];//num[v] 表示以v為根的子樹的節點數
int pid[MAXN];//pid[v]表示v對應的位置
int fpid[MAXN];//fpid和pid陣列相反
int son[MAXN];//重兒子
int pos;
void init()
{
    tot = 0;
    memset(head,-1,sizeof(head));
    pos = 1;//使用樹狀陣列,編號從頭1開始
    memset(son,-1,sizeof(son));
}
void addedge(int u,int v)
{
    edge[tot].to = v; edge[tot].next = head[u]; head[u] = tot++;
}
void dfs1(int u,int pre,int d){
    dep[u] = d;faz[u] = pre;num[u] = 1;
    for(int i = head[u];i != -1; i = edge[i].next){
        int v = edge[i].to;
        if(v != pre){
            dfs1(v,u,d+1);
            num[u] += num[v];
            if(son[u] == -1 || num[v] > num[son[u]])
                son[u] = v;
        }
    }
}
void getpos(int u,int sp){
    top[u] = sp;pid[u] = pos++;fpid[pid[u]] = u;
    if(son[u] == -1) return;
    getpos(son[u],sp);
    for(int i = head[u];i != -1;i = edge[i].next){
        int v = edge[i].to;
        if( v != son[u] && v != faz[u])
            getpos(v,v);
    }
}
//樹狀陣列
int c[MAXN];
int lowbit(int x){return x&-x;}
int sum(int x){
    int ret=0;
    while(x>0){
        ret+=c[x];
        x-=lowbit(x);
    }
    return ret;
}
void add(int x,int v){
    while(x<=n){
        c[x]+=v;
        x+=lowbit(x);
    }
}

//區間查詢把add改成區間查詢函式即可

void updatePath(int u,int v,int val){
    while(top[u]!=top[v]){
        if(dep[top[u]]>dep[top[v]])swap(u,v);
        add(pid[top[v]],val);
        add(pid[v]+1,-val);
        v=faz[top[v]];
    }
    if(dep[u]>dep[v])swap(u,v);
    
    add(pid[u],val);
    add(pid[v]+1,-val);
}
/********
 *修改邊權的程式碼中,邊權存放在,dep較大的那個節點中
 *尋找完lca後,if(u==v)return ans;否則將u改成son[u]
 *即update(pid[son[u]],pid[v]);
 *************/


int a[MAXN];
int main()
{
    //freopen("in.txt","r",stdin);
    while(~scanf("%d%d%d",&n,&M,&P)){
        init();
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        while(M--){
            scanf("%d%d",&l,&r);
            addedge(l,r);
            addedge(r,l);
        }
        dfs1(1,0,0);
        getpos(1,1);
        memset(c,0,sizeof c);
        for(int i=1;i<=n;i++){
            add(pid[i],a[i]);
            add(pid[i]+1,-a[i]);
        }
        //printf("ok\n");
        while(P--){
            scanf("%s",op);
            if(op[0]=='Q'){
                scanf("%d",&v);
                printf("%d\n",sum(pid[v]));
            }
            else{
                scanf("%d%d%d",&l,&r,&v);
                v=v*(op[0]=='I'?1:-1);
                updatePath(l,r,v);
            }
        }
    }
    return 0;
}

6、LCA

tarjan+並查集,離線

hdu 2586
tarjan+lca,適用於離線查詢,板子題。
使用dfs和tarjan前要memset(vis,0,sizeof(vis))
修改N,複雜度O(n+q)
*/


#include<bits/stdc++.h>
#define mp make_pair
using namespace std;
typedef pair<int,int> pii;
struct UFSet{
    static const int N=4e4+10;
    int fa[N];
    void init(){for(int i=1;i<N;i++)fa[i]=i;}
    int Find(int x){return fa[x]==x?x:fa[x]=Find(fa[x]);}
};

struct TarjanLca{
    static const int N=4e4+10;
    vector<pii> G[N];
    //vector<int> G[N];
    vector<pii> Q[N];
    int ans[N];
    bool vis[N],vvis[N];
    int dep[N];
    UFSet ufset;
    void init(){
        for(int i=0;i<N;i++)
            G[i].clear(),Q[i].clear();
        ufset.init();
        memset(ans,-1,sizeof ans);
        memset(vvis,0,sizeof vvis);
    }
    void Tarjan(int u){
        int sz=G[u].size();
        vis[u]=1;
        for(int i=0;i<sz;i++){
            int v=G[u][i].first;
            if(!vis[v])
            {
                Tarjan(v);
                int f1=ufset.Find(u);
                int f2=ufset.Find(v);
                //合併到父節點上
                ufset.fa[f2]=f1;
            }
        }
        vvis[u]=1;
        sz=Q[u].size();
        for(int i=0;i<sz;i++){
            int v=Q[u][i].first;int index=Q[u][i].second;
            if(vvis[v]){
                ans[index]=dep[u]+dep[v]-2*dep[ufset.Find(v)];
            }
        }
    }
    void dfs(int u){
        vis[u]=1;
        int sz=G[u].size();
        for(int i=0;i<sz;i++){
            int v=G[u][i].first;
            if(!vis[v]){//修改dep
                dep[v]=dep[u]+G[u][i].second;
                dfs(v);
            }
        }
    }
};



TarjanLca tjlca;

int main(){
    //freopen("in.txt","r",stdin);
    int n,q;
    int T;cin>>T;
    while(T--){
        cin>>n>>q;
        tjlca.init();
        int x,y,c;
        for(int i=1;i<n;i++){
            cin>>x>>y>>c;
            tjlca.G[x].push_back(mp(y,c));
            tjlca.G[y].push_back(mp(x,c));
        }
        for(int i=1;i<=q;i++){
            cin>>x>>y;
            tjlca.Q[x].push_back(mp(y,i));
            tjlca.Q[y].push_back(mp(x,i));
        }
        memset(tjlca.vis,0,sizeof tjlca.vis);//使用前記得memset;
        tjlca.dep[1]=0;
        tjlca.dfs(1);
        memset(tjlca.vis,0,sizeof tjlca.vis);//使用前記得memset;
        tjlca.Tarjan(1);
        for(int i=1;i<=q;i++)
            cout<<tjlca.ans[i]<<endl;
    }
    return 0;
}

倍增演算法

#include<bits/stdc++.h>
using namespace std;
const int N=10000+5;
const int logN=20;
vector <int> son[N];
int T,n,depth[N],fa[N][logN],in[N],a,b;
void dfs(int prev,int u){
    depth[u]=depth[prev]+1;
    fa[u][0]=prev;
    for (int i=1;i<logN;i++)
        fa[u][i]=fa[fa[u][i-1]][i-1];
    for (int i=0;i<son[u].size();i++)
        dfs(rt,son[u][i]);
}
int LCA(int x,int y){
    if (depth[x]<depth[y])
        swap(x,y);
    for (int i=logN-1;i>=0;i--)
        if (depth[x]-depth[y]>=(1<<i))
            x=fa[x][i];
    if (x==y)
        return x;
    for (int i=logN-1;i>=0;i--)
        if (fa[x][i]!=fa[y][i])
            x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}
int main(){
    scanf("%d",&T);
    while (T--){
        scanf("%d",&n);
        for (int i=1;i<=n;i++)
            son[i].clear();
        memset(in,0,sizeof in);
        for (int i=1;i<n;i++){
            scanf("%d%d",&a,&b);
            son[a].push_back(b);
            in[b]++;
        }
        depth[0]=-1;
        int rt=0;
        for (int i=1;i<=n&&rt==0;i++)
            if (in[i]==0)
                rt=i;
        dfs(0,rt);
        scanf("%d%d",&a,&b);
        printf("%d\n",LCA(a,b));
    }
    return 0;
}

7、LCT

#include<cstdio>
#include<cstdlib>
#define R register int
#define I inline void
#define lc c[x][0]
#define rc c[x][1]
#define G ch=getchar()
#define in(z) G;\
    while(ch<'-')G;\
    z=ch&15;G;\
    while(ch>'-')z*=10,z+=ch&15,G;
const int N=300009;
int f[N],c[N][2],v[N],s[N],st[N];
bool r[N];
inline bool nroot(R x){//判斷節點是否為一個Splay的根(與普通Splay的區別1)
    return c[f[x]][0]==x||c[f[x]][1]==x;
}//原理很簡單,如果連的是輕邊,他的父親的兒子裡沒有它
I pushup(R x){//上傳資訊
    s[x]=s[lc]^s[rc]^v[x];
}
I pushr(R x){R t=lc;lc=rc;rc=t;r[x]^=1;}//翻轉操作
I pushdown(R x){//判斷並釋放懶標記
    if(r[x]){
        if(lc)pushr(lc);
        if(rc)pushr(rc);
        r[x]=0;
    }
}
I rotate(R x){//一次旋轉
    R y=f[x],z=f[y],k=c[y][1]==x,w=c[x][!k];
    if(nroot(y))c[z][c[z][1]==y]=x;c[x][!k]=y;c[y][k]=w;//額外注意if(nroot(y))語句,此處不判斷會引起致命錯誤(與普通Splay的區別2)
    if(w)f[w]=y;f[y]=x;f[x]=z;
    pushup(y);
}
I splay(R x){//只傳了一個引數,因為所有操作的目標都是該Splay的根(與普通Splay的區別3)
    R y=x,z=0;
    st[++z]=y;//st為棧,暫存當前點到根的整條路徑,pushdown時一定要從上往下放標記(與普通Splay的區別4)
    while(nroot(y))st[++z]=y=f[y];
    while(z)pushdown(st[z--]);
/*當然了,其實利用函式堆疊也很方便,代替上面幾行手動棧,就像這樣
I pushall(R x){
    if(nroot(x))pushall(f[x]);
    pushdown(x);
}*/
    while(nroot(x)){
        y=f[x];z=f[y];
        if(nroot(y))
            rotate((c[y][0]==x)^(c[z][0]==y)?x:y);
        rotate(x);
    }
    pushup(x);
}
I access(R x){//訪問
    for(R y=0;x;x=f[y=x])
        splay(x),rc=y,pushup(x);
}
I makeroot(R x){//換根
    access(x);splay(x);
    pushr(x);
}
inline int findroot(R x){//找根(在真實的樹中的)
    access(x);splay(x);
    while(lc)pushdown(x),x=lc;
    //splay(x);(在本模板中建議不寫)
    return x;
}
I split(R x,R y){//提取路徑
    makeroot(x);
    access(y);splay(y);
}
I link(R x,R y){//連邊
    makeroot(x);
    if(findroot(y)!=x)f[x]=y;
}
I cut(R x,R y){//斷邊
    makeroot(x);
    if(findroot(y)==x&&f[x]==y&&!rc)){
        f[x]=c[y][0]=0;
        pushup(y);
    }
}
int main()
{
    register char ch;
    R n,m,i,type,x,y;
    in(n);in(m);
    for(i=1;i<=n;++i){in(v[i]);}
    while(m--){
        in(type);in(x);in(y);
        switch(type){
        case 0:split(x,y);printf("%d\n",s[y]);break;
        case 1:link(x,y);break;
        case 2:cut(x,y);break;
        case 3:splay(x);v[x]=y;//先把x轉上去再改,不然會影響Splay資訊的正確性
        }
    }
    return 0;
}