P3313 [SDOI2014]旅行

传送门

树剖+动态开点线段树

对每种宗教开一个线段树,用来维护区间和,区间最大值

普通的线段树空间不够,所以要动态开点

因为宗教会改变,所以要有删除操作和插入操作

比如城市1从信仰a变成信仰b,那就把a的线段树上城市1删掉,在b线段树上插入城市1

询问就只要询问与旅行者同宗教的值就好了

怎么实现也不难,看看代码就懂了

然后我还搞了个垃圾回收的操作

把删掉的节点编号记录一下,如果有新增节点,编号优先用以前的

很容易实现

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}

const int N=2e5+7;
int n,m,w[N],c[N];

int fir[N<<1],from[N<<1],to[N<<1],cntt;
inline void add(int &a,int &b)
{
    from[++cntt]=fir[a];
    fir[a]=cntt; to[cntt]=b;
}

//以下为树剖预处理
int dep[N],sz[N],fa[N],son[N];
void dfs1(int x,int f)
{
    dep[x]=dep[f]+1; sz[x]=1; fa[x]=f;
    int mxsz=0;
    for(int i=fir[x];i;i=from[i])
    {
        int &v=to[i];
        if(v==f) continue;
        dfs1(v,x); sz[x]+=sz[v];
        if(sz[v]>mxsz)
            mxsz=sz[v],son[x]=v;
    }
}
int id[N],Top[N],tot;
void dfs2(int x,int topp)
{
    id[x]=++tot; Top[x]=topp;
    if(!son[x]) return;
    dfs2(son[x],topp);
    for(int i=fir[x];i;i=from[i])
    {
        int &v=to[i];
        if(v==fa[x]||v==son[x]) continue;
        dfs2(v,v);
    }
}
//以上为树剖预处理

//--------------------------线段树----------------------------------
struct node
{
    int sum,mx,lc,rc;//因为动态开点所以要记录左右儿子
    node() { sum=mx=lc=rc=0; }
}t[N<<4];
int cnt,rt[N];//rt是每个宗教的根节点
queue <int> q;//存回收的编号
inline void pushup(int &o)//更新当前节点
{
    t[o].sum=t[t[o].lc].sum+t[t[o].rc].sum;
    t[o].mx=max(t[t[o].lc].mx,t[t[o].rc].mx);
}
inline void clr(int &o)//回收垃圾
{
    t[o].sum=t[o].mx=t[o].lc=t[o].rc=0;
    q.push(o); o=0;
}
void ins(int &o,int l,int r,int &v/**/,int &pos/*位置*/)//插入 or 更新
{
    if(!o)//如果还没节点就新建一个
    {
        if(!q.empty()) o=q.front(),q.pop();//垃圾回收利用
        else o=++cnt;
    }
    if(l==r)//如果到叶子节点就更新并返回
    {
        t[o].sum=t[o].mx=v;
        return;
    }
    int mid=l+r>>1;
    if(pos<=mid) ins(t[o].lc,l,mid,v,pos);
    else ins(t[o].rc,mid+1,r,v,pos);
    pushup(o);//更新当前节点
}
void erase(int &o,int l,int r,int &pos)//删除
{
    if(l==r) { clr(o); return; }
    int mid=l+r>>1;
    if(pos<=mid) erase(t[o].lc,l,mid,pos);
    else erase(t[o].rc,mid+1,r,pos);

    if(t[o].lc||t[o].rc) pushup(o);//如果还有儿子就更新值
    else clr(o);//不然一起删了
}
int query_sum(int o,int l,int r,int &ql,int &qr)//询问区间sum
{
    if(!o) return 0;
    if(l>=ql&&r<=qr) return t[o].sum;
    int mid=l+r>>1,res=0;
    if(ql<=mid) res+=query_sum(t[o].lc,l,mid,ql,qr);
    if(qr>mid) res+=query_sum(t[o].rc,mid+1,r,ql,qr);
    return res;
}
int query_max(int o,int l,int r,int &ql,int &qr)//询问区间max
{
    if(!o) return 0;
    if(l>=ql&&r<=qr) return t[o].mx;
    int mid=l+r>>1,res=0;
    if(ql<=mid) res=max(res,query_max(t[o].lc,l,mid,ql,qr));
    if(qr>mid) res=max(res,query_max(t[o].rc,mid+1,r,ql,qr));
    return res;
}
//----------------------------线段树----------------------------------

void change_bel(int x,int y)//改变信仰
{
    erase(rt[c[x]],1,n,id[x]);
    c[x]=y;
    ins(rt[c[x]],1,n,w[x],id[x]);
}
void change_val(int x,int y)//改变价值
{
    w[x]=y;
    ins(rt[c[x]],1,n,w[x],id[x]);
}

void Q_sum(int x,int y)//询问树上两点间路径的同宗教的sum
{
    int res=0,be=c[x];//旅行者的宗教
    while(Top[x]!=Top[y])//正常的树剖询问
    {
        if(dep[Top[x]]<dep[Top[y]]) swap(x,y);
        res+=query_sum(rt[be],1,n,id[Top[x]],id[x]);
        x=fa[Top[x]];
    }
    if(dep[x]<dep[y]) swap(x,y);
    res+=query_sum(rt[be],1,n,id[y],id[x]);
    printf("%d\n",res);
}
void Q_max(int x,int y)//跟上面差不多
{
    int res=0,be=c[x];
    while(Top[x]!=Top[y])
    {
        if(dep[Top[x]]<dep[Top[y]]) swap(x,y);
        res=max(res,query_max(rt[be],1,n,id[Top[x]],id[x]));
        x=fa[Top[x]];
    }
    if(dep[x]<dep[y]) swap(x,y);
    res=max(res,query_max(rt[be],1,n,id[y],id[x]));
    printf("%d\n",res);
}

int main()
{
    n=read(); m=read();
    for(int i=1;i<=n;i++) w[i]=read(),c[i]=read();
    int a,b;
    for(int i=1;i<n;i++) a=read(),b=read(),add(a,b),add(b,a);
    dfs1(1,1); dfs2(1,1);
    for(int i=1;i<=n;i++) ins(rt[c[i]],1,n,w[i],id[i]);//处理好初始状态

    char ch[5];
    for(int i=1;i<=m;i++)
    {
        scanf("%s",ch); a=read(); b=read();
        if(ch[0]=='C')
        {
            if(ch[1]=='C') change_bel(a,b);
            else change_val(a,b);
        }
        else
        {
            if(ch[1]=='S') Q_sum(a,b);
            else Q_max(a,b);
        }
    }
    return 0;
}

 

posted @ 2018-10-11 09:05  LLTYYC  阅读(199)  评论(0编辑  收藏  举报