【bzoj 3531】 [Sdoi2014]旅行(树链剖分+树套树)

49 篇文章 0 订阅
33 篇文章 0 订阅

3531: [Sdoi2014]旅行

Time Limit: 20 Sec   Memory Limit: 512 MB
Submit: 1197   Solved: 576
[ Submit][ Status][ Discuss]

Description

 S国有N个城市,编号从1到N。城市间用N-1条双向道路连接,满足
从一个城市出发可以到达其它所有城市。每个城市信仰不同的宗教,如飞天面条神教、隐形独角兽教、绝地教都是常见的信仰。为了方便,我们用不同的正整数代表各种宗教,  S国的居民常常旅行。旅行时他们总会走最短路,并且为了避免麻烦,只在信仰和他们相同的城市留宿。当然旅程的终点也是信仰与他相同的城市。S国政府为每个城市标定了不同的旅行评级,旅行者们常会记下途中(包括起点和终点)留宿过的城市的评级总和或最大值。
    在S国的历史上常会发生以下几种事件:
”CC x c”:城市x的居民全体改信了c教;
”CW x w”:城市x的评级调整为w;
”QS x y”:一位旅行者从城市x出发,到城市y,并记下了途中留宿过的城市的评级总和;
”QM x y”:一位旅行者从城市x出发,到城市y,并记下了途中留宿过
的城市的评级最大值。
    由于年代久远,旅行者记下的数字已经遗失了,但记录开始之前每座城市的信仰与评级,还有事件记录本身是完好的。请根据这些信息,还原旅行者记下的数字。    为了方便,我们认为事件之间的间隔足够长,以致在任意一次旅行中,所有城市的评级和信仰保持不变。

Input

    输入的第一行包含整数N,Q依次表示城市数和事件数。
    接下来N行,第i+l行两个整数Wi,Ci依次表示记录开始之前,城市i的
评级和信仰。
    接下来N-1行每行两个整数x,y表示一条双向道路。
    接下来Q行,每行一个操作,格式如上所述。

Output

    对每个QS和QM事件,输出一行,表示旅行者记下的数字。

Sample Input

5 6
3 1
2 3
1 2
3 3
5 1
1 2
1 3
3 4
3 5
QS 1 5
CC 3 1
QS 1 5
CW 3 3
QS 1 5
QM 2 4

Sample Output

8
9
11
3

HINT

N,Q < =10^5    , C < =10^5


 数据保证对所有QS和QM事件,起点和终点城市的信仰相同;在任意时

刻,城市的评级总是不大于10^4的正整数,且宗教值不大于C。

Source

[ Submit][ Status][ Discuss]



因为每个城市的信仰不同,而旅人只能在与其起点信仰相同的城市留宿,即在查询时,只能将与起点信仰相同的城市的评级计算进和或最大值,所以显然,建一棵线段树不能满足要求,所以要按不同信仰,每种信仰建一棵线段树(参考主席树的方式),每棵线段树维护两个值:和与最大值。查询时直接在相同信仰的线段树中查询即可


#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int a[200010],next[200010],p[100010],tot;//next数组
int fa[100010],son[100010],size[100010],dep[100010];//fa是存每个节点的父节点,son存每个节点的重儿子,size存每个节点所控制的节点数(包含它自己),dep表示每个节点的深度 
int trn1[100010],top[100010],tip;//trn1表示每个节点在线段树里的编号,top表示每个节点所在链的头结点 
int tree1[40000010],tree2[40000010],L[4000010],R[4000010],root[100010],g;//tree1是维护和的线段树,tree2是维护最大值的线段树,L是存每棵线段树的左子树的根的标号,R是存每棵线段树的右子树的根的编号,root存每棵线段树的根的编号(即当前线段树是信仰编号为几的线段树) 
int n,m,w[100010],v[100010];//w是每个城市的评级,v是每个城市的信仰 
void add(int x,int y)
{
    tot++; a[tot]=y; next[tot]=p[x]; p[x]=tot;
    tot++; a[tot]=x; next[tot]=p[y]; p[y]=tot;
}
 
void dfs1(int now,int father,int h)
{
    fa[now]=father; dep[now]=h; size[now]=1;
    int u=p[now];
    while (u!=0)
     {
        int y=a[u];
        if (y!=father) 
          {
            dfs1(y,now,h+1);
            size[now]+=size[y];
            if (son[now]==-1||size[y]>size[son[now]]) son[now]=y;
          }
        u=next[u];
     }
}
void dfs2(int now,int tp)
{
    top[now]=tp; trn1[now]=++tip;
    if (son[now]==-1) return;
    dfs2(son[now],tp);
    int u=p[now];
    while (u!=0)
     {
        int y=a[u];
        if (y!=fa[now]&&y!=son[now])
         dfs2(y,y);
        u=next[u];
     }
}//树链剖分部分
 
void updata(int rt)
{
    tree1[rt]=tree1[L[rt]]+tree1[R[rt]];
    tree2[rt]=max(tree2[L[rt]],tree2[R[rt]]);
}
void build(int &rt,int l,int r,int now,int val)
{
    if (!rt) rt=++g;//如果这是一种新的信仰,则新开一棵线段树 
    if (l==r)
     { tree1[rt]=val; tree2[rt]=val; return;}
    int mid=(l+r)>>1;
    if (now<=mid) build(L[rt],l,mid,now,val);
      else build(R[rt],mid+1,r,now,val);
    updata(rt);
}//建树和单点修改 
int ask1(int rt,int al,int ar,int l,int r)
{
    if (!rt) return 0;
    if (al<=l&&ar>=r) return tree1[rt];
    int mid=(l+r)>>1,ans=0;
    if (al<=mid) ans+=ask1(L[rt],al,ar,l,mid);
    if (ar>mid) ans+=ask1(R[rt],al,ar,mid+1,r);
    return ans;
}//查询区间和 
int ask2(int rt,int al,int ar,int l,int r)
{
    if (!rt) return 0;
    if (al<=l&&ar>=r) return tree2[rt];
    int mid=(l+r)>>1,ans=-0x7fffffff;
    if (al<=mid) ans=max(ans,ask2(L[rt],al,ar,l,mid));
    if (ar>mid) ans=max(ans,ask2(R[rt],al,ar,mid+1,r));
    return ans;
}//查询区间最大值 
int a1(int x,int y,int rt)
{
    int ans=0;
    while (top[x]!=top[y])
     {
        if (dep[top[x]]<dep[top[y]]) swap(x,y);
        ans+=ask1(rt,trn1[top[x]],trn1[x],1,n);
        x=fa[top[x]];
     }
    if (dep[x]>dep[y]) swap(x,y);
    ans+=ask1(rt,trn1[x],trn1[y],1,n);
    return ans;
}//查询区间和的预处理(因为x,y可能不在一条链里,序号也不一定谁大谁小,所以要跑一个类似一个lca的东西,当他们不在一条链上时,先将较深的那段跑完,再跑在同一条链上的。 和原来区间修改时相似) 
int a2(int x,int y,int rt)
{
    int ans=-0x7ffffffff;
    while (top[x]!=top[y])
     {
        if (dep[top[x]]<dep[top[y]]) swap(x,y);
        ans=max(ans,ask2(rt,trn1[top[x]],trn1[x],1,n));
        x=fa[top[x]];
     }
    if (dep[x]>dep[y]) swap(x,y);
    ans=max(ans,ask2(rt,trn1[x],trn1[y],1,n));
    return ans;
}//查询区间最大值的预处理 
 
int main()
{
    int i;
    memset(son,-1,sizeof(son));
    scanf("%d%d",&n,&m);
    for (i=1;i<=n;++i) scanf("%d%d",&w[i],&v[i]);
    for (i=1;i<n;++i) {int x,y; scanf("%d%d",&x,&y); add(x,y);}
    dfs1(1,0,1); dfs2(1,1);
    for (i=1;i<=n;++i) 
       build(root[v[i]],1,n,trn1[i],w[i]);//按不同信仰建多棵线段树,同一信仰的点放在同一棵线段树里 
    for (i=1;i<=m;++i)
     {
        char c[10]; int x,y;
        scanf("%s%d%d",c,&x,&y);
        if (c[0]=='C')
          if (c[1]=='C')
             {build(root[v[x]],1,n,trn1[x],0); build(root[y],1,n,trn1[x],w[x]); v[x]=y; continue;}//将编号为x的城市的信仰改为y,即将原来线段树中城市x的评级改为0,再在信仰为y的线段树里加上城市x的评级,最后别忘将存x的信仰的v改为y,不然查询时会出错 
           else {build(root[v[x]],1,n,trn1[x],y); w[x]=y; continue;}//将编号为x的城市的评级改为y,直接更改即可,不过改完后,也要将w[x]更改成当前值 
         else
          if (c[1]=='S')
             printf("%d\n",a1(x,y,root[v[x]]));//查询旅人从城市x到城市y记录的城市评级的和(只能记录与城市x信仰相同的),直接在以信仰v[x]为根的线段树里查询tree1即可 
            else printf("%d\n",a2(x,y,root[v[x]]));//查询旅人从城市x到城市y记录的城市评级最大值(只能记录与城市x信仰相同的),直接在以信仰v[x]为根的线段树里查询tree2即可 
     }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值