ACM树和数据结构

ACM树和数据结构

树这个结构是真的神奇,很多算法和复杂一点的数据结构,都是以树为基础的,
因为树结构的可以再很快的时间(logn)去解决很多问题。
比如 去做一个dfs搜索,实际上就是一个状态空间上的搜索树。
然后就是线段树、平衡树、动态树、Trie树(前缀树)。都用来解决一些特殊的问题。

下面从头开是讲树

0、树的结构和性质

大部分数据结构书上都说了,总结一下,就是1对n,
我们一般用树的根来表示一棵树(标号), 比如0是根
那么0是一棵树, 如果0->1, 0->2, 2->3,
那么1,2也是一棵树, 同时也是0的两颗子树

性质

  • 子树概念, 子树的概念, 对应了递归或者说动态规划的子问题。
  • DAG性质, 在确定跟的情况下, 树其实也是一个DAG, 并且更简单, 所以树的DP更为简单
  • 树链唯一性, 任意两个点(u,v)在树上有且只有一条路径, 这条路径称作(u,v)的树链,且树链经过LCA(u,v)

实现上:三种实现方式。

  • 父指针
    fa[N];

  • 儿子指针(用于M叉树)
    son[N][M];

  • 儿子兄弟指针(其实和图的邻接表一样,用于一般的非多叉树和森林);

1、基本元素的求法

  • 直径

    题目poj1849

    #include
    #include
    #include
    #define rep(i,l,r) for(int i=l;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;
        }
    }
    
  • 点分治
    poj 1741

    点分治, 就是将重心作为跟来进行DFS,以此来解决计数问题, 
    计数问题往往也是通过DP的方法求解。
    

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
    //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
#include
#include
#include
#include
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序

树链剖分就使用到了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 
#include 
#include 
#include 
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
#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 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
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
#include
#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;
}

你可能感兴趣的:(树,数据结构)