树链剖分和树状数组的应用

题目大意:有N个兵营,M条道路,3种操作:

增加(减少)i,j及i到j路径上的所有兵营的人数K: I i j k

查询军营i的人数: Q i

详见HDU 3966 Aragorn's Story

输入:第一行N M q, (1 ≤Q ≤ 100000)(M=N-1)

第二行 N个int (代表编号1--n每个军营的人数)

之后是q行操作

输出:每个Q操作对应一行查询结果

思路点拨:1.解决路径上的所有军营都有哪些用到树链剖分(找最大公共祖先)

2.应为原题中Q较大,可能会频繁更新区间(特殊方法将树链变为可类似区间操作的形式)

关键点:树状数组与树链剖分的结合

因为要区间(先将路径看作区间)更新的次数比较多,用树状数组,差分形式看待原数据。

找到两点之间的路径用树链剖分求最大公共祖先可以做到。

tree数组对每个点进行重新编号,保证同义重链上点的标号连续,只要保证每次更新操作是在同一重链上进行的就可以将军营的编号看作一段区间(只是操作方式上的等效)

存储方式:

路径用vector存

树链剖分:f:父节点,d:深度,son:重儿子,size:子树

#include<bits/stdc++.h>
using namespace std;

const int N 50000+7     //军营最大数量

开头:略

vector<int>G[N];
void add(int u,int v)
{
    G[u].push_back(v);
    G[v].push_back(u);
}

路径信息的存储,用vector存

add函数将两点连接(tips:在函数内实现了双向连接,不用再使用时调用两次实现双向连接)

int f[N],d[N],son[N],size[N];
void dfs1(int x,int fath)
{
    f[x]=fath;    d[x]=d[fath]+1;
    size[x]=1;    son[x]=0;
    for(int to,i;i<G[x].size();i++)
    {
        to=G[x][i];
        if(tp==fath) continue;
        dfs1(to,x);
        size[x]+=size[to];
        if(size[son[x]]<size[to]) son[x]=to;
    }
}

int top[N],tree[N],cnt;
void dfs2(int x,int topx)
{
    top[x]=topx;
    tree[x]=++cnt;
    if(son[x]) dfs2(son[x],topx);
    for(int i=0;i<G[x].size();i++)
    {
        if(G[x][i]!=f[x]&&G[x][i]!=son[x])
            dfs2(G[x][i],G[x][i]);
    }
}

树链剖分的两次dfs

第二次实现时增加了数据:tree数组和cnt,dfs2内增加了一行tree[x]=++cnt;

目的是对军营再次编号,保证同一重链上编号的连续性,方便树状数组的构建

对于lca部分的实现在下一部分的第二个update函数中,与树状数组更新相结合

#define lowbit(x) (x&-x)
int sum[N]
void update(int index,int val)
{
    for(int i=index;i<=n;i+=lowbit(i)) sum[i]+=val;
}
void getSum(int index)
{
    int ans=0;
    for(int i=index;i;i-=lowbit(i)) ans+=sum[i];
    return ans;
}

int update(int x, int y, int k)
{
    int fx=top[x],fy=top[y];
    while(fx!=fy)
    {
        if(d[fx]<d[fy]) swap(x,y),swap(fx,fy);
        update(tree[fx],k),update(tree[x]+1,-k);
        x=f[fx],fx=top[x];
    }
    if(d[x]>d[y]) swap(x,y);
    update(tree[x],k),update(tree[y]+1,-k);
}

树状数组的实现

sum[N]是树状数组

第一个update实现单点跟新

getSum实现区间查询

第二个update实现区间更新(原数组用差分数组形式表示)(包含树链剖分lca部分)

相比于单纯的lca在每次跟新x为f[top[x]]之前,将x到其重链头的部分先更新(这部分一定在中间路径上)

int w[N],n,m,q;

int main()
{
    while(cin>>n>>m>>q){
        memset(sum,0,sizeof(sum));
        
        for(int i=1;i<=n;i++) scanf("%d",&w[i]);
        for(int i=1,u,v;i<=m;i++)
        {
            scanf("%d%d",&u,&v);
            add(u,v);
        }
        
        init();
        dfs1(1,0);
        dfs2(1,1);
        
        for(int i=1;i<=n;i++) update(tree[i],w[i]),update(tree[i]+1,-w[i]);
        
        char ch,s[10];
        int a,b,k;
        while(q--)
        {
            scanf("%s",s);
            ch=s[0];
            if(ch=='Q'){
                scanf("%d",&a);
                printf("%d\n",getSum(tree[a]));
            }else
            {
                scanf("%d%d%d",&a,&b,&k);
                if(ch=='D') k=-k;
                update(a,b,k);
            }
        }
        for(int i=1;i<=n;i++) G[i].clear();
    }
    return 0;
}

主函数部分:略

关键点:树状数组与树链剖分的结合

因为要区间(先将路径看作区间)更新的次数比较多,用树状数组,差分形式看待原数据。

找到两点之间的路径用树链剖分求最大公共祖先可以做到。

tree数组对每个点进行重新编号,保证同义重链上点的标号连续,只要保证每次更新操作是在同一重链上进行的就可以将军营的编号看作一段区间(只是操作方式上的等效)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值