树链剖分——BZOJ4196/Luogu2146 [Noi2015]软件包管理器

题面:BZOJ4196 Luogu2146
首先软件包形成了一棵树,然后题中的软件包只有两种状态:装了与没被装
如果要装某个软件包,那么从这个包到根的软件包都要装
如果要卸某个软件包,那么以这个软件包为根的子树软件包都不能装
所以这道题实际要我们干什么:树上修改一条链的值,修改子树的值,求一条链的和,求子树和
然后发现要求的操作和洛谷的树剖模板题几乎一样?!
这里写图片描述
那么就把Luogu的模板拿来改一下就好了(逃
不过那是不可能的)两题题面还是有很大的不同的
这道NOI原题要求边询问边修改,这个倒不是难题,直接跟在询问后面好了
关于子树相关操作其实也很简单,dfs序之后节点x的整棵子树区间范围连续且确定在sx[x]~sx[x]+s[x]-1(sx[x]为x转到线段树里的编号,s[x]表示以x为根子树大小)
关键在于树剖之后的线段树处理的细节部分。。。(我卡了很久吧。。。
我是把软件包的状态看成0/1,0表示装了1表示没装(这样有利于链上操作而且子树操作也不受太大影响)
然后查的时候把1搞出来,边查边改,计算答案,细节很多

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iostream>
#include<cstdlib>
#include<string>
#include<ctime>
#include<queue>
#include<climits>
using namespace std;
char c[11];
int n,nedge=0,p[200001],nex[200001],head[200001];
int fa[100001],deep[100001],s[100001],son[100001],top[100001];
int sx[100001],xs[100001],ne=0;
int lt[400001],rt[400001],t[400001],add[400001];
inline void addedge(int a,int b){p[++nedge]=b;nex[nedge]=head[a];head[a]=nedge;}
inline void dfs(int x,int Fa,int dep){
    fa[x]=Fa;deep[x]=dep;s[x]=1;
    for(int k=head[x];k;k=nex[k]){
        if(p[k]==Fa)continue;
        dfs(p[k],x,dep+1);s[x]+=s[p[k]];
        if(!son[x]||s[p[k]]>s[son[x]])son[x]=p[k];
    }
}
inline void dfss(int x,int bh){
    top[x]=bh;sx[x]=++ne;xs[sx[x]]=x;
    if(!son[x])return;
    dfss(son[x],bh);
    for(int k=head[x];k;k=nex[k])if(p[k]!=son[x]&&p[k]!=fa[x])dfss(p[k],p[k]);
}
inline void clean(int nod){//lazy标记下传
    if(add[nod]==-1)return;
    if(lt[nod]!=rt[nod]){
        t[nod*2]=add[nod]*(rt[nod*2]-lt[nod*2]+1);
        add[nod*2]=add[nod];
        t[nod*2+1]=add[nod]*(rt[nod*2+1]-lt[nod*2+1]+1);
        add[nod*2+1]=add[nod];
    }
    add[nod]=-1;
}
inline void build(int l,int r,int nod){
    lt[nod]=l;rt[nod]=r;
    if(l==r){t[nod]=add[nod]=1;return;}
    int mid=l+r>>1;
    build(l,mid,nod*2);build(mid+1,r,nod*2+1);
    t[nod]=t[nod*2]+t[nod*2+1];add[nod]=1;
}
inline void xg(int i,int j,int nod,int w){
    clean(nod);    
    if(lt[nod]>=i&&rt[nod]<=j){t[nod]=(rt[nod]-lt[nod]+1)*w;add[nod]=w;return;}
    int mid=lt[nod]+rt[nod]>>1;
    if(i<=mid)xg(i,j,nod*2,w);
    if(j>mid)xg(i,j,nod*2+1,w);
    t[nod]=t[nod*2]+t[nod*2+1];
}
inline int ssum(int i,int j,int nod){
    clean(nod);
    if(lt[nod]>=i&&rt[nod]<=j)return t[nod];
    int mid=lt[nod]+rt[nod]>>1,ans=0;
    if(i<=mid)ans+=ssum(i,j,nod*2);
    if(j>mid)ans+=ssum(i,j,nod*2+1);
    return ans;
}
inline int fsum(int x,int y){
    int fx=top[x],fy=top[y],ans=0;
    while(fx!=fy){
        if(deep[fx]<deep[fy])swap(fx,fy),swap(x,y);
        ans+=ssum(sx[fx],sx[x],1);xg(sx[fx],sx[x],1,0);//边查边改1
        x=fa[fx];fx=top[x];
    }
    if(deep[x]>deep[y])swap(x,y);
    ans+=ssum(sx[x],sx[y],1);xg(sx[x],sx[y],1,0);//边查边改2
    return ans;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        int x;scanf("%d",&x);
        addedge(x,i);addedge(i,x);
    }
    dfs(0,-1,1);dfss(0,0);build(1,n,1);
    int m;scanf("%d",&m);
    for(int i=1;i<=m;i++){
        int x;scanf("%s%d",c+1,&x);
        if(c[1]=='i')printf("%d\n",fsum(0,x));
        else{
            printf("%d\n",s[x]-ssum(sx[x],sx[x]+s[x]-1,1));
            xg(sx[x],sx[x]+s[x]-1,1,1);//处理子树部分
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值