【BZOJ3531】旅行(SDOI2014)-树链剖分+动态加点线段树

测试地址:旅行
做法:这一题需要用到树链剖分+动态开点线段树。
我们先把树上的问题转到区间上考虑:如何查询一个区间内信某种宗教的所有城市的评级之和或最大值?容易想到的方案是,对于每种宗教建一棵线段树维护,然而这个空间开销实在是太庞大了,无法承受。这个时候就要使用线段树的变体——动态开点线段树。
动态开点线段树这个东西长得有点像主席树,但它们有的时候能解决完全不同的问题。简单的说,动态开点线段树的思想就是:当一个节点被用到时才把这个点建出来。由于题目中的修改只涉及单点修改,所以显然这样做空间和时间都是 O(nlogn)  的,可以承受。
那么转到树上就很简单了,直接树链剖分即可。
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 100000
using namespace std;
int n,q,w[N+10],c[N+10];
int tot=0,first[N+10]={0},totp,tim;
int sum[20*N+10],mx[20*N+10],ch[20*N+10][2];
int fa[N+10],siz[N+10],son[N+10],dep[N+10],top[N+10],pos[N+10];
struct edge
{
    int v,next;
}e[2*N+10];

int read()
{
    char c;
    int ans=0;
    c=getchar();
    while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9') ans=ans*10+c-'0',c=getchar();
    return ans;
}

void addedge(int a,int b)
{
    e[++tot].v=b;
    e[tot].next=first[a];
    first[a]=tot;
}

void dfs1(int v)
{
    siz[v]=1,son[v]=0;
    for(int i=first[v];i;i=e[i].next)
        if (e[i].v!=fa[v])
        {
            fa[e[i].v]=v;
            dep[e[i].v]=dep[v]+1;
            dfs1(e[i].v);
            if (siz[e[i].v]>siz[son[v]]) son[v]=e[i].v;
            siz[v]+=siz[e[i].v];
        }
}

void dfs2(int v,int tp)
{
    top[v]=tp;pos[v]=++tim;
    if (son[v]) dfs2(son[v],tp);
    for(int i=first[v];i;i=e[i].next)
        if (e[i].v!=fa[v]&&e[i].v!=son[v])
            dfs2(e[i].v,e[i].v);
}

void pushup(int no)
{
    sum[no]=sum[ch[no][0]]+sum[ch[no][1]];
    mx[no]=max(mx[ch[no][0]],mx[ch[no][1]]);
}

void modify(int &no,int l,int r,int k,int val)
{
    if (!no) no=++totp,ch[no][0]=ch[no][1]=0,sum[no]=0;
    if (l==r)
    {
        sum[no]=mx[no]=val;
        return;
    }
    int mid=(l+r)>>1;
    if (k<=mid) modify(ch[no][0],l,mid,k,val);
    else modify(ch[no][1],mid+1,r,k,val);
    pushup(no);
}

int segquery(int no,int l,int r,int s,int t,int mode)
{
    if (!no) return 0;
    if (l>=s&&r<=t) return mode?mx[no]:sum[no];
    int mid=(l+r)>>1,ans=0;
    if (!mode)
    {
        if (s<=mid) ans+=segquery(ch[no][0],l,mid,s,t,mode);
        if (t>mid) ans+=segquery(ch[no][1],mid+1,r,s,t,mode);
    }
    else
    {
        if (s<=mid) ans=max(ans,segquery(ch[no][0],l,mid,s,t,mode));
        if (t>mid) ans=max(ans,segquery(ch[no][1],mid+1,r,s,t,mode));
    }
    return ans;
}

int query(int x,int y,int mode)
{
    int ans=0,C=c[x];
    while(top[x]!=top[y])
    {
        if (dep[top[x]]<dep[top[y]]) swap(x,y);
        if (!mode) ans+=segquery(C,1,n,pos[top[x]],pos[x],mode);
        else ans=max(ans,segquery(C,1,n,pos[top[x]],pos[x],mode));
        x=fa[top[x]];
    }
    if (dep[x]>dep[y]) swap(x,y);
    if (!mode) ans+=segquery(C,1,n,pos[x],pos[y],mode);
    else ans=max(ans,segquery(C,1,n,pos[x],pos[y],mode));
    return ans;
}

int main()
{
    n=read(),q=read();
    for(int i=1;i<=n;i++)
        w[i]=read(),c[i]=read();
    for(int i=1;i<n;i++)
    {
        int a,b;
        a=read(),b=read();
        addedge(a,b),addedge(b,a);
    }
    fa[1]=siz[0]=dep[1]=0;
    dfs1(1);
    tim=0;
    dfs2(1,1);

    sum[0]=mx[0]=0;
    for(int i=1;i<=n;i++) ch[i][0]=ch[i][1]=0,sum[i]=0;
    totp=n;
    for(int i=1;i<=n;i++)
        modify(c[i],1,n,pos[i],w[i]);

    for(int i=1;i<=q;i++)
    {
        char op[3];
        int x,y;
        scanf("%s",op);
        x=read(),y=read();
        if (op[0]=='C')
        {
            if (op[1]=='C')
            {
                modify(c[x],1,n,pos[x],0);
                modify(y,1,n,pos[x],w[x]);
                c[x]=y;
            }
            if (op[1]=='W')
            {
                modify(c[x],1,n,pos[x],y);
                w[x]=y;
            }
        }
        if (op[0]=='Q'&&op[1]=='S') printf("%d\n",query(x,y,0));
        if (op[0]=='Q'&&op[1]=='M') printf("%d\n",query(x,y,1));
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值