HDU 4918 Query on the subtree(动态点分治+树状数组)

题意

给定一棵 \(n\) 个节点的树,每个节点有点权。完成 \(q\) 个操作——操作分两种:修改点 \(x\) 的点权、查询与 \(x\) 距离小于等于 \(d\) 的权值总和。

\(1 \leq n,q \leq 10^5\)

思路

从最简单的情况分析——只有一次查询。当然一遍 \(O(n)\)\(\text{dfs}\) 可以直接写,不过要用点分治写的话,\(\text{solve}\) 函数直接容斥一下就可以了。

如果多个询问呢?其实在回答关于点 \(x\) 的询问时,其实只需要计算管辖 \(x\) 的所有重心的答案。我们只需要将点分治的过程记录下来,查询只查管辖 \(x\) 的重心,就可以在 \(\log n\) 的复杂度内回答一次询问了。

具体的实现每道题略有区别,但具体思路大致相同。别忘了我们是从一次查询作优化,那么我们对于一个点,记录它到重心的距离;对于每个重心开一个数组,表示管辖范围内距离为 \(d\) 的节点权值总和,然后前缀和一下就变成了距离小于等于 \(d\) 的权值总和,由于还有容斥的部分,故符号也要记录。若节点 \(x\) 询问为 \(d\) ,对于某一级重心 \(C\) ,距离为 \(dis\) ,对应前缀和数组 \(A\) ,对应符号为 \(s\ (s\in\{1,-1\})\) ,那么 \(x\)\(C\) 的贡献就是 \(s\cdot A[d-dis]\)

而带上修改其实也没什么区别,只要把前缀和换成树状数组,然后每次修改,对于某一级重心 \(C\) ,在树状数组的 \(dis\) 位置做修改即可。

动态点分治就是把点分治的过程用适当容器去维护的算法。

代码

#include<bits/stdc++.h>
#define FOR(i,x,y) for(int i=(x),i##END=(y);i<=i##END;++i)
#define DOR(i,x,y) for(int i=(x),i##END=(y);i>=i##END;--i)
typedef long long LL;
using namespace std;
const int N=1e5+5;
template<const int maxn,const int maxm>struct Linked_list
{
    int head[maxn],to[maxm],nxt[maxm],tot;
    Linked_list(){clear();}
    void clear(){memset(head,-1,sizeof(head));tot=0;}
    void add(int u,int v){to[++tot]=v,nxt[tot]=head[u],head[u]=tot;}
    #define EOR(i,G,u) for(int i=G.head[u];~i;i=G.nxt[i])
};
struct FenwickTree
{
    #define lowbit(x) ((x)&-(x))
    vector<int>c;int n;
    void build(int _n){c.clear();FOR(i,0,n=_n+1)c.push_back(0);}
    void update(int k,int val){for(k++;k<=n;k+=lowbit(k))c[k]+=val;}
    int query(int k){int res=0;for(k=min(k+1,n);k>0;k^=lowbit(k))res+=c[k];return res;}
    #undef lowbit
};
Linked_list<N,N<<1>G;
FenwickTree FT[N*2];int Fc;
int Fid[N][45],dis[N][45],lv[N];bool sgn[N][45];
int sz[N];bool mark[N];
int pw[N],n,q;

void CFS(int u,int f,int tot,int &C,int &Mi)
{
    sz[u]=1;int res=0;
    EOR(i,G,u)
    {
        int v=G.to[i];
        if(v==f||mark[v])continue;
        CFS(v,u,tot,C,Mi);
        sz[u]+=sz[v];
        res=max(res,sz[v]);
    }
    res=max(res,tot-sz[u]);
    if(res<Mi)C=u,Mi=res;
}
void dfs_init(int u,int f,int D,bool s)
{
    Fid[u][++lv[u]]=Fc,dis[u][lv[u]]=D,sgn[u][lv[u]]=s;
    EOR(i,G,u)
    {
        int v=G.to[i];
        if(v==f||mark[v])continue;
        dfs_init(v,u,D+1,s);
    }
}
int dfs_dep(int u,int f,int d)
{
    int res=d;
    EOR(i,G,u)
    {
        int v=G.to[i];
        if(v==f||mark[v])continue;
        res=max(res,dfs_dep(v,u,d+1));
    }
    return res;
}
void dac(int u,int tot)
{
    int Mi=1e9;
    CFS(u,0,tot,u,Mi);
    mark[u]=1;
    FT[++Fc].build(dfs_dep(u,0,0));
    dfs_init(u,0,0,1);
    EOR(i,G,u)
    {
        int v=G.to[i];
        if(mark[v])continue;
        FT[++Fc].build(dfs_dep(v,u,1));
        dfs_init(v,u,1,0);
        dac(v,sz[u]>sz[v]?sz[v]:tot-sz[u]);
    }
}

void update(int u,int val)
{
    FOR(i,1,lv[u])
    {
        int v=Fid[u][i],w=dis[u][i];
        FT[v].update(w,val);
    }
}
int query(int u,int d)
{
    int res=0;
    FOR(i,1,lv[u])
    {
        int v=Fid[u][i],w=dis[u][i];bool s=sgn[u][i];
        if(s)res+=FT[v].query(d-w);
        else res-=FT[v].query(d-w);
    }
    return res;
}

int main()
{
    while(~scanf("%d%d",&n,&q))
    {
        G.clear();
        FOR(i,1,n)scanf("%d",&pw[i]);
        FOR(i,1,n-1)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            G.add(u,v),G.add(v,u);
        }
        Fc=0;
        memset(lv,0,sizeof(lv));
        memset(mark,0,sizeof(mark));
        dac(1,n);
        FOR(i,1,n)update(i,pw[i]);
        while(q--)
        {
            char str[5];int x,y;
            scanf("%s%d%d",str,&x,&y);
            if(str[0]=='!')
            {
                update(x,y-pw[x]);
                pw[x]=y;
            }
            else if(str[0]=='?')printf("%d\n",query(x,y));
        }
    }
    return 0;
}

转载于:https://www.cnblogs.com/Paulliant/p/10188484.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值