树代码&题解整活

1、LCA

1.1 LCA模板

运用倍增思想,向根方向寻找,倍增跳跃。

#include <bits/stdc++.h>
using namespace std;
const int  N = 5E5 + 5;
int f[N][20], s, n, m, x, y, dep[N], b[N];
vector <int> g[N];
void dfs(int xx)
{
    for(int i = 1; i <= 16; i++)
    {
        f[xx][i] = f[f[xx][i - 1]][i - 1]; 
    }
    for(int i = 0; i < g[xx].size(); i++)
    {
        int temp = g[xx][i];
        if(!b[temp])
        {
            b[temp] = 1;
            dep[temp] = dep[xx] + 1;
            f[temp][0] = xx;
            dfs(temp);
        }
    }
}
int LCA(int x, int y)
{
    if(dep[x] < dep[y])
    {
        swap(x, y);
    }
    int temp = dep[x] - dep[y];
    for(int i = 16; i >= 0; i--)
    {
        if((1 << i) & temp)
        {
            x = f[x][i];
        }
    }
    if(x == y)
    {
        return x;
    }
    for(int i = 16; i >= 0; i--)
    {
        if(f[x][i] != f[y][i])
        {
            x = f[x][i];
            y = f[y][i];
        }
    }
    return f[x][0];
}
int main()
{
    cin >> n >> m >> s;
    for(int i = 1; i < n; i++)
    {
        cin >> x >> y;
        g[x].push_back(y);
        g[y].push_back(x);
    }
    dep[s] = 1;
    b[s] = 1;
    dfs(s);
    while(m--)
    {
        cin >> x >> y;
        cout << LCA(x, y) << endl;
    }
}
View Code

2、树上前缀和-博客

寻找树上任意两点的距离,做法之一为树上前缀和

将两个点到根的距离之和相加即为答案。

2.1 模板

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
using namespace std;
const int N = 200005;
const int M = 25;
int Q, n, m, head[N], dou[N][M], cnt, dep[N], b[N], k, total[N], wei[N];
struct node {
    int u, v, next, sum;
} ed[N];
void add_edge(int u, int v, int k) {
    cnt++;
    ed[cnt].u = u;
    ed[cnt].v = v;
    ed[cnt].next = head[u];
    ed[cnt].sum = k;
    head[u] = cnt;
}
void dfs(int xx) {
    for (int i = 1; i <= 20; i++) {
        dou[xx][i] = dou[dou[xx][i - 1]][i - 1];
    }

    for (int i = head[xx]; i != 0; i = ed[i].next) {
        int temp = ed[i].v;

        if (b[temp] == 0) {
            b[temp] = 1;
            dep[temp] = dep[xx] + 1;
            total[temp] = ed[i].sum + total[xx];
            dou[temp][0] = xx;
            dfs(temp);
        }
    }
}
int LCA(int x, int y) {
    if (dep[x] < dep[y])
        swap(x, y);

    int temp = dep[x] - dep[y];

    for (int i = 20; i >= 0; i--) {
        if (1 << i & temp) {
            x = dou[x][i];
        }
    }

    if (x == y)
        return x;

    for (int i = 20; i >= 0; i--) {
        if (dou[x][i] != dou[y][i]) {
            x = dou[x][i];
            y = dou[y][i];
        }
    }

    return dou[x][0];
}
int main() {
    cin >> n;
    cin >> m;

    for (int i = 1; i < n; i++) {
        int x, y, k;
        scanf("%d%d%d", &x, &y, &k);
        add_edge(x, y, k);
        add_edge(y, x, k);
    }

    b[1] = 1;
    dep[1] = 1;
    dfs(1);

    while (m--) {
        int x, y;
        scanf("%d%d", &x, &y);
        int lca = LCA(x, y);
        cout << total[x] + total[y] - total[lca] - total[lca] << endl;
    }

    return 0;
}
View Code

 

2.2 JLOI2009]二叉树问题

 

上行边数$*2+$ 下行边数$*1$,可以把整个树的每一条边看成权值均为$1$ 的树,跑前缀和,左边到根的距离$*2+$ 右边到根的距离$*1$。

在求前缀和的过程中顺便把树的深度、宽度一把搞掉。

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
using namespace std;
vector <int> mapp[105];
int n,anslen[105],len[105],maxlen,dep[105],b[105],maxdep,maxnlong=1,dou[10005][25]; 
void dfs(int x)
{
    for(int i=1;i<=15;i++)
    {
        dou[x][i]=dou[dou[x][i-1]][i-1];
    }
    for(int i=0;i<mapp[x].size();i++)
    {
        if(!b[mapp[x][i]])
        {
            int temp=mapp[x][i]; 
            dep[temp]=1+dep[x];
            dou[temp][0]=x;
            b[mapp[x][i]]=1; 
            len[temp]=dep[temp];
            maxdep=max(dep[temp],maxdep);
            dfs(temp);
        }
    }
}
int LCA(int x,int y)
{
    if(dep[x]<dep[y])
    swap(x,y);
    int temp=dep[x]-dep[y];
    for(int i=16;i>=0;i--)
    {
        if(1<<i&temp)
        {
            x=dou[x][i];
        }
    }
    if(x==y)
    return x;
    for(int i=16;i>=0;i--)
    {
        if(dou[x][i]!=dou[y][i])
        {
            x=dou[x][i];
            y=dou[y][i];
        }
    }
    return dou[x][0];
}
int main()
{
    cin>>n;
    for(int i=1;i<=n-1;i++)
    {
        int x,y;
        cin>>x>>y;
        mapp[x].push_back(y);
        mapp[y].push_back(x);
    }
    b[1]=1;
    dep[1]=1;
    maxdep=1;
    len[1]=1;
    dfs(1);
    int u,v;
    cin>>u>>v;
    int lca=LCA(u,v);
    cout<<maxdep<<endl;
    for(int i=1;i<=n;i++)
    {
        anslen[len[i]]+=1;
    }
    for(int i=1;i<=maxdep;i++)
    {
        maxlen=max(maxlen,anslen[i]);
    }
    cout<<maxlen<<endl;
    cout<<(dep[u]-dep[lca])*2+(dep[v]-dep[lca]);
    return 0;
}
View Code

 

2.3 [AHOI2008]紧急集合 / 聚会


给定三个点,找到一个点,使得该点到题目所给的三个点的距离最小。

可以使用树上前缀和来写,需要一些常数上的优化,当然注意卡常

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
using namespace std;
const int N=1000005;
const int M=25; 
int dep[N],cnt,dou[N][M],n,m,x,y,z,b[N];
int head[N];
struct node{
    int u,v,next;
}ed[N];
inline int read()
{
    int f=1;
    int xx=0;
    char c=getchar();
    while(!isdigit(c))
    {
        if(c=='-')
        f*=-1;
        c=getchar();
    }
    while(isdigit(c))
    {
        xx=xx*10+c-'0';
        c=getchar();
    }
    return xx*f;
}
void add_edge(int u,int v)
{
    ed[++cnt].u=u;
    ed[cnt].v=v;
    ed[cnt].next=head[u];
    head[u]=cnt;    
}
void dfs(int xx)
{
    for(int i=1;i<=16;i++)
    {
        dou[xx][i]=dou[dou[xx][i-1]][i-1];
    }
    for(int i=head[xx];i!=0;i=ed[i].next)
    {
        if(b[ed[i].v]==0)
        {
            int temp=ed[i].v;
            b[temp]=1;
            dep[temp]=dep[xx]+1;
            dou[temp][0]=xx;
            dfs(temp); 
        }
    }
}
int LCA(int x,int y)
{
    if(dep[x]<dep[y])
    swap(x,y);
    int temp=dep[x]-dep[y];
    for(int i=16;i>=0;i--)
    {
        if(1<<i&temp)
        x=dou[x][i];
    } 
    if(x==y)
    return y;
    for(int i=16;i>=0;i--)
    {
        if(dou[x][i]!=dou[y][i])
        {
            x=dou[x][i];
            y=dou[y][i];
        }
    }
    return dou[x][0];
}
int main()
{
    n=read();
    m=read();
    for(int i=1;i<n;i++)
    {
        x=read();
        y=read();
        add_edge(x,y);
        add_edge(y,x); 
    }
    dep[1]=1;
    b[1]=1;
    dfs(1);
    while(m--)
    {
        x=read();
        y=read();
        z=read();
        int xy=LCA(x,y);
        int yz=LCA(y,z);
        int xz=LCA(x,z);
        int ans=0,ansmoney=0;
        if(xy==yz)
        ans=xz;
        else if(xy==xz)
        ans=yz;
        else if(yz==xz)
        ans=xy;
        cout<<ans<<" ";
        ansmoney+=dep[x]+dep[y]-dep[xy]*2;
        ansmoney+=dep[y]+dep[z]-dep[yz]*2;
        ansmoney+=dep[x]+dep[z]-dep[xz]*2;
        ansmoney/=2;
        cout<<ansmoney<<endl;
    }
    return 0;
}
View Code

 

 3、树上差分

为前缀和的逆运算,所以与前缀和做法类同。

3.1 模板

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
using namespace std;
const int N=100005;
const int M=25;
int cnt,head[N],dou[N][M],total[N],n,m,dep[N],b[N];
struct node{
    int u,v,w,next;
}ed[N];
void add_edge(int u,int v)
{
    cnt++;
    ed[cnt].u=u;
    ed[cnt].v=v;
    ed[cnt].next=head[u];

    head[u]=cnt;
}
void dfs(int xx)
{
    for(int i=1;i<=20;i++)
    {
        dou[xx][i]=dou[dou[xx][i-1]][i-1];
    }
    for(int i=head[xx];i;i=ed[i].next)
    {
        int temp=ed[i].v;
        if(!b[temp])
        {
            b[temp]=1;
            dep[temp]=dep[xx]+1;
            dou[temp][0]=xx;
            dfs(temp);
        }
    }
} 
int LCA(int x,int y)
{
    if(dep[x]<dep[y])
    {
        swap(x,y);
    }
    int temp=dep[x]-dep[y];
    for(int i=20;i>=0;i--)
    {
        if((1<<i)&temp)
        {
            x=dou[x][i];
        }
    }
    if(x==y)
    return x;
    for(int i=20;i>=0;i--)
    {
        if(dou[x][i]!=dou[y][i])
        {
            x=dou[x][i];
            y=dou[y][i];
        }
    }
    return dou[x][0];
}
void dfs2(int xx)
{
    for(int i=head[xx];i;i=ed[i].next)
    {
        int temp=ed[i].v;
        if(!b[temp])
        {
            b[temp]=1;
            dfs2(temp);
            total[xx]+=total[temp];
        }
    }
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<n;i++)
    {
        int x,y;
        cin>>x>>y;
        add_edge(x,y);
        add_edge(y,x);
    }
    b[1]=1;
    dep[1]=1;
    dfs(1);
    memset(b,0,sizeof b);
    while(m--)
    {
        int xx,yy;
        cin>>xx>>yy;
        int lca=LCA(xx,yy);    
        total[xx]++;
        total[yy]++;
        total[lca]--;
        total[dou[lca][0]]--;
    }
    b[1]=1;
    dfs2(1);
    int maxn=0;
    for(int i=1;i<=n;i++)
    {
        maxn=max(maxn,total[i]);
    }
    cout<<maxn;
    return 0;
}
View Code

 

3.2 松鼠的新家


做法与差分模板类似。

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
using namespace std;
const int N=1000005;
const int M=25;
int cnt,n,head[N],fa[N][M],dep[N],b[N],total[N],a[N],bt[N];
struct node{
    int u,v,next;
}ed[N]; 
void add_edge(int u,int v)
{
    cnt++;
    ed[cnt].u=u;
    ed[cnt].v=v;
    ed[cnt].next=head[u];
    head[u]=cnt;
}
void dfs(int xx)
{
    for(int i=1;i<=20;i++)
    {
        fa[xx][i]=fa[fa[xx][i-1]][i-1];
    }
    for(int i=head[xx];i;i=ed[i].next)
    {
        int temp=ed[i].v;
        if(!b[temp])
        {
            b[temp]=1;
            dep[temp]=dep[xx]+1;
            fa[temp][0]=xx;
            dfs(temp);
        }
    }
}
void dfs2(int xx)
{
    for(int i=head[xx];i;i=ed[i].next)
    {
        int temp=ed[i].v;
        if(!b[temp])
        {
            b[temp]=1;
            dfs2(temp);
            total[xx]+=total[temp];
        }
    }
}
int LCA(int x,int y)
{
    if(dep[x]<dep[y])
    {
        swap(x,y);
    }
    int temp=dep[x]-dep[y];
    for(int i=20;i>=0;i--)
    {
        if(1<<i&temp)
        {
            x=fa[x][i];
        }
    }
    if(x==y)
    {
        return x;
    }
    for(int i=20;i>=0;i--)
    {
        if(fa[x][i]!=fa[y][i])
        {
            x=fa[x][i];
            y=fa[y][i];
        }
    }
    return fa[x][0];
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    cin>>a[i];
    for(int i=1;i<n;i++)
    {
        int x,y;
        cin>>x>>y;
        add_edge(x,y);
        add_edge(y,x); 
    } 
    b[1]=1;
    dep[1]=1;
    dfs(1);
    memset(b,0,sizeof b);
    for(int i=2;i<n;i++)
    {
        int x=a[i-1];
        int y=a[i];
        if(!b[x])
        {
            total[x]++;
            b[x]=1;
        }
        else
        {
            total[fa[x][0]]++;
        }
        if(!b[y])
        {
            total[y]++;
            b[y]=1; 
        } 
        else
        {
            total[fa[y][0]]++;
        }
        int lca=LCA(x,y);
        total[lca]--;
        total[fa[lca][0]]--; 
    }
    int x=a[n-1];
    int y=a[n];
    total[fa[x][0]]++;
    total[fa[y][0]]++;
    int lca=LCA(x,y);
    total[lca]--;
    total[fa[lca][0]]--; 
    memset(b,0,sizeof b);
    b[1]=1;
    dfs2(1);
    for(int i=1;i<=n;i++)
    {
        cout<<total[i]<<endl;
    }
    return 0;
}
View Code

 

4、数的直径

一些无向边且权值均为正整数构成的树上(可以无根),距离最长的2个点的距离为树的直径。

 

posted @ 2021-03-31 20:20  panjx  阅读(108)  评论(0编辑  收藏  举报