【bzoj 3531】 [Sdoi2014]旅行(树链剖分+树套树)
3531: [Sdoi2014]旅行
Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 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
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
9
11
3
HINT
N,Q < =10^5 , C < =10^5
数据保证对所有QS和QM事件,起点和终点城市的信仰相同;在任意时
刻,城市的评级总是不大于10^4的正整数,且宗教值不大于C。
Source
【因为每个城市的信仰不同,而旅人只能在与其起点信仰相同的城市留宿,即在查询时,只能将与起点信仰相同的城市的评级计算进和或最大值,所以显然,建一棵线段树不能满足要求,所以要按不同信仰,每种信仰建一棵线段树(参考主席树的方式),每棵线段树维护两个值:和与最大值。查询时直接在相同信仰的线段树中查询即可】
#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;
}