LCA的应用

1、多次询问求树上两个点的距离

例题:hdu2586 http://acm.hdu.edu.cn/showproblem.php?pid=2586

分析:LCA的板子题,关键利用dist[u,v]=dist[1,u]+dist[1,v]-2*dist[1,lca(u,v)];

Ac code:

#include<bits/stdc++.h>
using namespace std;
const int maxn=4e4+10;
typedef long long ll;
int head[maxn],tot,T;
struct Edge
{
    int v,w,nxt;
} edge[maxn<<1];
void addedge(int u,int v,int w)
{
    edge[tot].v=v;
    edge[tot].w=w;
    edge[tot].nxt=head[u];
    head[u]=tot++;

    edge[tot].v=u;
    edge[tot].w=w;
    edge[tot].nxt=head[v];
    head[v]=tot++;
}
int f[maxn][20],d[maxn];
ll dist[maxn];
void dfs(int u,int fa)///预处理
{
   for(int i=head[u];~i;i=edge[i].nxt)
   {
       int v=edge[i].v;
       if(v==fa) continue;
       d[v]=d[u]+1;
       dist[v]=dist[u]+edge[i].w;
       f[v][0]=u;
       for(int j=1;j<=T;j++)
          f[v][j]=f[f[v][j-1]][j-1];
       dfs(v,u);
   }
}
int lca(int x,int y)///logn求x,y的lca
{
    if(d[x]>d[y]) swap(x,y);
    for(int i=T;i>=0;i--)
        if(d[f[y][i]]>=d[x]) y=f[y][i];
    if(x==y) return x;
    for(int i=T;i>=0;i--)
        if(f[y][i]!=f[x][i]) x=f[x][i],y=f[y][i];
    return f[x][0];
}
int main()
{
    int n,t,m;
    scanf("%d",&t);
    int u,v,w;
    while(t--)
    {
        memset(head,-1,sizeof head);
        memset(d,0,sizeof d);
        memset(dist,0,sizeof dist);
        scanf("%d%d",&n,&m);
        T=log(n)/log(2)+1;
        for(int i=1; i<=n-1; i++)
        {
            scanf("%d%d%d",&u,&v,&w);
            addedge(u,v,w);
        }
        dfs(1,-1);
        while(m--){
            scanf("%d%d",&u,&v);
            printf("%lld\n",dist[u]+dist[v]-2*dist[lca(u,v)]);
        }
    }
    return 0;
}

例题:POJ3417  http://poj.org/problem?id=3417

分析:

1、“主要边”构成一棵树,“附加边”则是非树边。把一条附加边(x,y)添加到主要边构成的树中,会与树上x,y之间的路径一起形成一个环。如果第一步选择切断x,y之间 路径上的某条边,那么第二步就必须切断附加边(x,y),才能令Dark被斩为不联通的两部分。

2、我们称每条附加边(x,y)都把树上x,y之间的路径上的每条边“覆盖了一次"。我们只需统计出每条”主要边“被覆盖了多少次,若第一步把被覆盖0次的主要边切断,则第二步可任意选一条附加边切断。若第一步把被覆盖了一次的主要边切断,则第二步方法唯一。若第一步把被覆盖了两次及以上的主要边切断,则如何操作都不能击败Dark。

Ac code:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cmath>
using namespace std;
const int maxn=1e5+10;
typedef long long ll;
struct Edge{
    int v,w,nxt;
}edge[maxn<<1];
int head[maxn],tot;
void addedge(int u,int v,int w)
{
    edge[tot].v=v;
    edge[tot].w=w;
    edge[tot].nxt=head[u];
    head[u]=tot++;
}
int F[maxn],val[maxn];
int f[maxn][20],d[maxn],T;
void dfs1(int u,int fa)
{
    for(int i=head[u];~i;i=edge[i].nxt){
        int v=edge[i].v;
        if(v==fa) continue;
        d[v]=d[u]+1;
        f[v][0]=u;
        for(int j=1;j<=T;j++)
            f[v][j]=f[f[v][j-1]][j-1];
        dfs1(v,u);
    }
}
int lca(int x,int y)
{
    if(d[x]>d[y]) swap(x,y);
    for(int i=T;i>=0;i--)
        if(d[f[y][i]]>=d[x]) y=f[y][i];
    if(x==y) return x;
    for(int i=T;i>=0;i--)
        if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    return f[x][0];
}
void dfs2(int u,int fa)
{
    F[u]=val[u];
    for(int i=head[u];~i;i=edge[i].nxt){
        int v=edge[i].v;
        if(v==fa) continue;
        dfs2(v,u);
        F[u]+=F[v];
    }
}
int main()
{
    int n,m,u,v;
    memset(head,-1,sizeof head);
    scanf("%d%d",&n,&m);
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&u,&v);
        addedge(u,v,1);
        addedge(v,u,1);
    }
    T=(int)(log(n)/log(2))+1;
    d[1]=1;
    dfs1(1,-1);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&u,&v);
        val[u]++,val[v]++;
        val[lca(u,v)]-=2;
    }
    dfs2(1,-1);
    ll ans=0;
    for(int i=1;i<=n;i++)
        if(F[i]==0&&i!=1)
          ans+=m;
        else if(F[i]==1)
          ans++;
    printf("%lld\n",ans);
    return 0;
}

 

3、树上差分

例题:https://loj.ac/problem/146

分析:支持三种操作

1、修改路径上的结点值

2、查询单个结点权值

3、查询一个结点子树的权值和

对于操作1,显然用树上差分,用val[i]表示结点i的差分数组值,则i结点的真正改变值为以i为子树的所有结点的val数组和,如要修改(x,y)路径上的所有结点值+z,则val[x]+=z,val[y]+=z,val[lca(x,y)]-=z,val[fa[lca(x,y)]-=z;类似区间上的差分操作,对于结点fa[lca(x,y)](表示x,y的最近公共祖先的父节点)以上的结点的子树的val和都是不变的,只有[x,y]路径上的结点的子树val和加上了z,不在该路径上的都没变。

操作2,单点权值=差分数组前缀和+原本值,树状数组即可实现

操作3,结点i的子树的权值和与结点深度有关,维护关于结点深度*修改值的树状数组(具体看代码)...

Ac code:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;;
typedef long long ll;
ll a[maxn];
template<class T>
void read(T &x) {
    x = 0;
    int f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
    x=x * f;
}
int tot,n,cnt;
struct Edge{
    int v,nxt;
}edge[maxn<<1];
int head[maxn];
int d[maxn],T;
int fa[maxn],dfn[maxn];///dfn为每个结点dfs序的编号
ll sum[maxn];
int sze[maxn],top[maxn],son[maxn];
void init()
{
    tot=0;
    cnt=0;
    memset(head,-1,sizeof head);
    T=log(n)/log(2)+1;
}
struct Tree{
    ll c[maxn<<1];
    int lowbit(int x)
    {
        return x&(-x);
    }
    void update(int x,ll val)
    {
        while(x<=cnt){
            c[x]+=val;
            x+=lowbit(x);
        }
    }
    ll getsum(int x)
    {
        ll ans=0;
        while(x>0){
            ans+=c[x];
            x-=lowbit(x);
        }
        return ans;
    }
    ll query(int u){
        return getsum(dfn[u]+sze[u]-1)-getsum(dfn[u]-1);
    }
}A,B;
void change(int u,ll val)
{
    A.update(dfn[u],val);
    B.update(dfn[u],val*d[u]);
}
void addedge(int u,int v)
{
    edge[tot].v=v;
    edge[tot].nxt=head[u];
    head[u]=tot++;
}
void dfs1(int u,int faa)
{
    dfn[u]=++cnt;
    sze[u]=1;
    for(int i=head[u];~i;i=edge[i].nxt){
        int v=edge[i].v;
        if(v==faa) continue;
        d[v]=d[u]+1;
        fa[v]=u;
        son[u]=v;
        dfs1(v,u);
        sze[u]+=sze[v];
        sum[u]+=sum[v];
        if(sze[v]>sze[son[u]]) son[u]=v;
    }
}
//int lca(int x,int y)
//{
//    if(d[x]>d[y]) swap(x,y);
//    for(int i=T;i>=0;--i)
//        if(d[f[y][i]]>=d[x]) y=f[y][i];
//    if(x==y) return x;
//    for(int i=T;i>=0;--i)
//        if(f[y][i]!=f[x][i]) x=f[x][i],y=f[y][i];
//    return f[x][0];
//}
///树链剖分求LCA
void DFS(int u, int TOP) {
    top[u] = TOP;
    if (son[u])
        DFS(son[u], TOP);
    for (int i = head[u]; ~i; i =edge[i].nxt)
        if (!top[edge[i].v])
            DFS(edge[i].v, edge[i].v);
}
int lca(int x, int y) {
    while (top[x] != top[y])
        if (d[top[x]] > d[top[y]])
            x = fa[top[x]];
        else
            y = fa[top[y]];
    return d[x] < d[y] ? x : y;
}
int main()
{
    int m,r;
    read(n),read(m),read(r);
    init();
    for(int i=1;i<=n;i++)
        read(a[i]),sum[i]=a[i];
    int u,v;
    for(int i=1;i<=n-1;i++){
        read(u),read(v);
        addedge(u,v);
        addedge(v,u);
    }
    dfn[0]=++cnt;///0号下标树状数组不能用,故要去掉
    d[r]=1;
    dfs1(r,0);
    DFS(r,r);
    int x,y,op;
    ll z;
    while(m--){
        read(op),read(x);
        if(op==1){
            read(y),read(z);
            int anc=lca(x,y);
            change(x,z);
            change(y,z);
            change(anc,-z);
            change(fa[anc],-z);
        }
        else if(op==2){
            printf("%lld\n",A.query(x)+a[x]);
        }
        else{
            printf("%lld\n",B.query(x)-A.query(x)*(d[x]-1)+sum[x]);
        }
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值