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;
}
            
                    
                
                
            
        
浙公网安备 33010602011771号