[SDOI2014]旅行
[SDOI2014]旅行https://www.luogu.org/problemnew/show/P3313
题目描述
\(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\),并记下了途中留宿过的城市的评级最大值。
由于年代久远,旅行者记下的数字已经遗失了,但记录开始之前每座城市的信仰与评级,还有事件记录本身是完好的。请根据这些信息,还原旅行者记下的数字。 为了方便,我们认为事件之间的间隔足够长,以致在任意一次旅行中,所有城市的评级和信仰保持不变。
输入格式:
输入的第一行包含整数\(N,Q\)依次表示城市数和事件数。 接下来\(N\)行,第\(i+1\)行两个整数\(W_i\),\(C_i\)依次表示记录开始之前,城市\(i\)的评级和信仰。 接下来\(N-1\)行每行两个整数\(x,y\)表示一条双向道路。 接下来\(Q\)行,每行一个操作,格式如上所述。
输出格式:
对每个\(QS\)和\(QM\)事件,输出一行,表示旅行者记下的数字。
输入样例:
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
输出样例:
8
9
11
3
说明
\(N\),\(Q < =10^5\) , \(C < =10^5\)
数据保证对所有\(QS\)和\(QM\)事件,起点和终点城市的信仰相同;在任意时刻,城市的评级总是不大于\(10^4\)的正整数,且宗教值不大于\(C\)。
树剖
此题第一想法对每种宗教维护一个线段树
但空间肯定开不下,而且浪费了许多空间
于是...
看题解,问大佬,发现了一种神奇的操作...
"动态"线段树
对于一个点的修改,只会影响到\(logn\)个节点,所以一开始一个空线段树,动态的添加点,对每个点维护根节点,左右儿子(不再是\(root<<1\)和\(root<<1|1\)了),总空间\([(N+Q)*logN]\)
#define RG register
#include<cstdio>
#include<iostream>
using namespace std;
const int N=1e5+5;
inline int read()
{
RG int x=0,w=1;RG char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
return x*w;
}
int n,m,cnt,ct,tot,Q;
int last[N],w[N],c[N],dfn[N],size[N],son[N],fa[N],dep[N],top[N],rt[N];//rt[]每个宗教对应线段树的根节点
int sum[N*40],Max[N*40],ls[N*40],rs[N*40];
struct edge{int to,next;}e[N<<1];
inline void insert(int u,int v)
{
e[++cnt]=(edge){v,last[u]};last[u]=cnt;
e[++cnt]=(edge){u,last[v]};last[v]=cnt;
}
void dfs1(int now)
{
size[now]=1;
for(int i=last[now];i;i=e[i].next)
{
int v=e[i].to;
if(v==fa[now])continue;
fa[v]=now;
dep[v]=dep[now]+1;
dfs1(v);
size[now]+=size[v];
if(size[v]>size[son[now]])son[now]=v;
}
}
void dfs2(int now,int Top)
{
top[now]=Top;dfn[now]=++ct;
if(son[now])dfs2(son[now],Top);
for(int i=last[now];i;i=e[i].next)
{
int v=e[i].to;
if(v==fa[now]||v==son[now])continue;
dfs2(v,v);
}
}
void Modify(int &root,int l,int r,int s,int k)//注意&,这个操作动态维护了左右儿子和根节点
{
if(!root)root=++tot;
if(l==r){sum[root]=Max[root]=k;return;}
int mid=(l+r)>>1;
if(s<=mid)Modify(ls[root],l,mid,s,k);
else Modify(rs[root],mid+1,r,s,k);
sum[root]=sum[ls[root]]+sum[rs[root]];
Max[root]=max(Max[ls[root]],Max[rs[root]]);
}
int Query(int root,int l,int r,int ll,int rr,int op)
{
if(ll<=l&&r<=rr)
{
if(op)return sum[root];
return Max[root];
}
int mid=(l+r)>>1,Ans=0;
if(ll<=mid)
{
if(op)Ans+=Query(ls[root],l,mid,ll,rr,op);
else Ans=max(Ans,Query(ls[root],l,mid,ll,rr,op));
}
if(mid<rr)
{
if(op)Ans+=Query(rs[root],mid+1,r,ll,rr,op);
else Ans=max(Ans,Query(rs[root],mid+1,r,ll,rr,op));
}
return Ans;
}
inline int Query_Tree(int x,int y,int op)
{
int Ans=0,k=rt[c[x]];//k表示根节点
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]])swap(x,y);
if(op)Ans+=Query(k,1,n,dfn[top[x]],dfn[x],op);
else Ans=max(Ans,Query(k,1,n,dfn[top[x]],dfn[x],op));
x=fa[top[x]];
}
if(dep[x]>dep[y])swap(x,y);
if(op)Ans+=Query(k,1,n,dfn[x],dfn[y],op);
else Ans=max(Ans,Query(k,1,n,dfn[x],dfn[y],op));
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++)
{
RG int a=read(),b=read();
insert(a,b);
}
dep[1]=1;
dfs1(1);
dfs2(1,1);
for(int i=1;i<=n;i++)Modify(rt[c[i]],1,n,dfn[i],w[i]);//初始化线段树
RG char act[5];
RG int x,y;
while(Q--)
{
scanf("%s",act);
x=read();y=read();
if(act[1]=='C')
{
Modify(rt[c[x]],1,n,dfn[x],0);//将原来节点变成0
Modify(rt[y],1,n,dfn[x],w[x]);//将宗教y所在线段树中新建节点
c[x]=y;//修改宗教
}
if(act[1]=='W')Modify(rt[c[x]],1,n,dfn[x],y),w[x]=y;//修改权值
if(act[1]=='S')printf("%d\n",Query_Tree(x,y,1));//op==1查询SUM
if(act[1]=='M')printf("%d\n",Query_Tree(x,y,0));//op==0查询MAX
}
return 0;
}