SPOJ Query on a tree VI 树链剖分 树状数组

15 篇文章 0 订阅
4 篇文章 0 订阅
链接

https://vjudge.net/problem/SPOJ-QTREE6

题解

写得我脑壳疼,参考了不少题解,加深了对树剖的理解

W [ u ] W[u] W[u]维护当前局势下,如果 u u u为白色, u u u为根的子树中和 u u u联通的个数
B [ u ] B[u] B[u]维护当前局势下,如果 u u u为黑色, u u u为根的子树中和 u u u联通的个数
那么,对于询问操作,向上找到最远的与 u u u同色的节点 x x x,那么 W / B [ x ] W/B[x] W/B[x]就是答案

然后是修改操作
不妨设将u的颜色从白改成黑
u u u的父节点往上走,走到第一个黑点 v 1 v_1 v1,设路径 p a t h 1 path_1 path1 f a [ u ] → v 1 fa[u]→v_1 fa[u]v1,然后将 p a t h 1 path_1 path1上所有点的 W W W值减去 W [ u ] W[u] W[u]
同样地,从 u u u的父节点往上走,走到第一个白点 v 2 v_2 v2,设路径 p a t h 2 path_2 path2 f a [ u ] → v 2 fa[u]→v_2 fa[u]v2,然后将 p a t h 2 path_2 path2上所有点的 B B B值加上 B [ u ] B[u] B[u]

上面这些很好理解,只剩下两个问题,如果向上查找“最远的同色”和“最近的异色”
首先,“最远的同色”可以转化为“最近的异色”沿着原路径向下一步
所以,只要解决“最近的异色”即可

0表示白色,1表示黑色
树链剖分是一段一段向上的,如果整段是白色,和必为0,如果整段是黑色,和必为线段的长度
利用上述性质向上找到目标点所在的区间后,再在区间中二分查找即可

下面是选择数据结构

在解决“最近的异色”中,用到了区间求和,然后题目要求是单点修改,所以树状数组可以解决
在修改操作中,需要整段修改,但是答案是单点查询,所以树状数组可以解决
综上,需要3个树状数组就可以解决

注意的问题

在整段的修改 p a t h path path过程中,要利用树剖的性质,一段一段修改!!!
所以修改的复杂度为 O ( l o g 2 n ) O(log^2n) O(log2n)

总复杂度为 O ( n ∗ l o g 2 n ) O(n*log^2n) O(nlog2n)

代码
#include<bits/stdc++.h>
#define N 100010
#define INF 0x3f3f3f3f
#define eps 1e-10
#define pi 31592653589793
#define P 1000000007 
#define LL long long
#define pb push_back
#define fi first
#define se second
#define cl clear
#define si size
#define lb lower_bound
#define ub upper_bound
#define mem(x) memset(x,0,sizeof x)
#define sc(x) scanf("%d",&x)
#define scc(x,y) scanf("%d%d",&x,&y)
#define sccc(x,y,z) scanf("%d%d%d",&x,&y,&z)
using namespace std;
vector<int> a[N];
int n,m,cnt,rt,tg,pre,son[N],top[N],id[N],fa[N],d[N],rk[N],sz[N],g[3][N],c[N];

void dfs1(int x,int ffa){
    sz[x]=1;
    for(auto i:a[x]) if (i!=ffa){
        fa[i]=x; d[i]=d[x]+1; dfs1(i,x); sz[x]+=sz[i];
        if (sz[i]>sz[son[x]]) son[x]=i;
    }
}

void dfs2(int x,int t){
    top[x]=t; id[x]=++cnt; rk[cnt]=x;
    if (!son[x]) return;
    dfs2(son[x],t);
    for (auto i:a[x]) if (i!=son[x] && i!=fa[x]) dfs2(i,i);
}

inline int cal(int *a,int x){
    int res=0; for (int i=x;i>0;i-=i&-i) res+=a[i]; return res;
}

inline void up(int *a,int x,int d){
    for (int i=x;i<N;i+=i&-i) a[i]+=d;
}

inline void upseg(int *a,int x,int y,int d){
    while(top[x]!=top[y]){
        up(a,id[top[y]],d); up(a,id[y]+1,-d);
        y=fa[top[y]];
    }
    up(a,id[x],d);up(a,id[y]+1,-d);
}

inline int sumseg(int *a,int x,int y){
    return cal(a,y)-cal(a,x-1);
}

int find(int x,int fg){ 
    int l,r,ans; fg^=1;
    while(x){
        int t=sumseg(g[2],id[top[x]],id[x]);
        if (t!=(id[x]-id[top[x]]+1)*fg)break;
        pre=id[top[x]];
        x=fa[top[x]];
    }
    if (!x) return 1;
    l=id[top[x]]; r=id[x];
    while(l<=r){
        int t=l+r>>1;
        int tm=sumseg(g[2],t,r);
        if (tm!=(r-t+1)*fg) ans=t,l=t+1;else r=t-1;
    }
    if (ans!=id[x]) pre=ans+1;
    return rk[ans];
}

int main(){
    sc(n);
    for (int i=1,x,y;i<n;i++){
        scc(x,y);
        a[x].pb(y),a[y].pb(x);
    }
    dfs1(1,-1);
    dfs2(1,1);
    for (int i=1;i<=n;i++) upseg(g[0],i,i,sz[i]),upseg(g[1],i,i,1);
    sc(m);
    while(m--){
        int op,x,v;
        scc(op,x); 
        if (op){
            int w=cal(g[0],id[x]),b=cal(g[1],id[x]);
            if (c[x]){
                if (x>1){
                    v=find(fa[x],0); upseg(g[1],v,fa[x],-b);
                    v=find(fa[x],1); upseg(g[0],v,fa[x],w);
                }
                up(g[2],id[x],-1);
            }else{       
                if (x>1){
                    v=find(fa[x],1); upseg(g[0],v,fa[x],-w);
                    v=find(fa[x],0); upseg(g[1],v,fa[x],b);
                }         
                up(g[2],id[x],1);
            }
            c[x]^=1;
        }else{
            find(x,c[x]^1);
            printf("%d\n",cal(g[c[x]],pre));
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值