题目链接:https://loj.ac/problem/2195
解题思路
树链剖分+动态开点线段树。可以通过树链剖分将问题转化成询问序列上与起点相同宗教的权值总和及最大值,并且支持单点更新。这个问题可以用线段树解决,但一般线段树要开四倍空间,而且要维护不同宗教,所以空间肯定不够。但是我们可以动态开点,树上每个点最多对应logn个点,所以动态开点最多也就nlogn个点,空间就够了,就是牺牲了常数的时间去开点,在这是可以接受的。
AC代码
#include <iostream>
#include <stdio.h>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=1e5+5;
const int maxm=2e7+5;
const int inf=0x3f3f3f3f;
struct node
{
int to,next;
}edge[maxn<<1];//链式前向星
int head[maxn],num[maxn],root[maxn];
struct Node
{
int l,r,Max,Sum;//l是左儿子编号,r是右儿子的编号
}tree[maxm];
int cnt,n,q,len;
int get()//快读
{
char c;
int sign=1;
while((c=getchar())<'0'||c>'9')
if(c=='-')
sign=-1;
int res=c-'0';
while((c=getchar())>='0'&&c<='9')
res=res*10+c-'0';
return res*sign;
}
void add(int x,int y)
{
edge[++cnt].to=y;
edge[cnt].next=head[x];
head[x]=cnt;
}
int fa[maxn];//x在树中的父亲
int dep[maxn];//x在树中的深度
int size[maxn];//x的子树结点数(子树大小)
int son[maxn];//x的重儿子,即u->son[u]是重边
int top[maxn];//x所在重路径的顶部顶点(深度最小)
int seg[maxn];//x在线段树中的位置(下标)
int rev[maxn];//线段树中第x位置对应的树中结点编号,rev[seg[x]]=x
int w[maxn];
int zj[maxn];
int Ssum,Mmax;
void update(int &rt,int L,int R,int val,int pos)//val是评级,os你要添加的点的fs序
{
if(!rt)
rt=++len;
tree[rt].Max=max(tree[rt].Max,val);
tree[rt].Sum+=val;
if(L==R)
return ;
int mid=(L+R)>>1;
if(mid>=pos)
update(tree[rt].l,L,mid,val,pos);
else
update(tree[rt].r,mid+1,R,val,pos);
}
void remove(int &rt,int L,int R,int pos)
{
if(L==R)
{
tree[rt].Sum=tree[rt].Max=0;
return ;
}
int mid=(L+R)>>1;
if(mid>=pos)
remove(tree[rt].l,L,mid,pos);
else
remove(tree[rt].r,mid+1,R,pos);
tree[rt].Sum=tree[tree[rt].l].Sum+tree[tree[rt].r].Sum;
tree[rt].Max=max(tree[tree[rt].l].Max,tree[tree[rt].r].Max);
}
int querySum(int rt,int lb,int rb,int L,int R)
{
if(R<lb||L>rb)
return 0;
if(L<=lb&&R>=rb)
return tree[rt].Sum;
int mid=(lb+rb)>>1;
return querySum(tree[rt].l,lb,mid,L,R)+querySum(tree[rt].r,mid+1,rb,L,R);
}
int queryMax(int rt,int lb,int rb,int L,int R)
{
if(R<lb||L>rb)
return 0;
if(L<=lb&&R>=rb)
return tree[rt].Max;
int mid=(lb+rb)>>1;
return max(queryMax(tree[rt].l,lb,mid,L,R),queryMax(tree[rt].r,mid+1,rb,L,R));
}
void dfs1(int u,int f)//第一遍dfs,算出fa[],dep[],size[],son[]
{
size[u]=1;
fa[u]=f;
dep[u]=dep[f]+1;
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==f)
continue;
dfs1(v,u);
size[u]+=size[v];//计算size
if(size[v]>size[son[u]])//求重儿子
son[u]=v;
}
}
void dfs2(int u,int f)//第二遍dfs,算出top[],seg[],rev[]
{
if(son[u])//先走重儿子,使重路径在线段树中的位置连续
{
seg[son[u]]=++seg[0];//根无法在这里赋值,所以根要在主程序赋值
top[son[u]]=top[u];//如果(u,v)为重边,那么u和v在同一条重路径上
rev[seg[0]]=son[u];
dfs2(son[u],u);
}
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(top[v])//top[v]有值即是被遍历过
continue;
seg[v]=++seg[0];
rev[seg[0]]=v;
top[v]=v;//如果(u,v)是轻边,则v就是其所在重路径的顶部结点
dfs2(v,u);
}
}
void askSum(int x,int y,int zj)//路径询问
{
int fx=top[x],fy=top[y];
while(fx!=fy)
{
if(dep[fx]<dep[fy])//选择深度较大的往上跳
{
swap(x,y);
swap(fx,fy);
}
Ssum+=querySum(root[zj],1,n,seg[fx],seg[x]);//重路径对应区间[seg[fx],seg[x]]
x=fa[fx];
fx=top[x];
}
if(dep[x]>dep[y])
swap(x,y);
Ssum+=querySum(root[zj],1,n,seg[x],seg[y]);
}
void askMax(int x,int y,int zj)
{
int fx=top[x],fy=top[y];
while(fx!=fy)
{
if(dep[fx]<dep[fy])//选择深度较大的往上跳
{
swap(x,y);
swap(fx,fy);
}
Mmax=max(Mmax,queryMax(root[zj],1,n,seg[fx],seg[x]));//重路径对应区间[seg[fx],seg[x]]
x=fa[fx];
fx=top[x];
}
if(dep[x]>dep[y])
swap(x,y);
Mmax=max(Mmax,queryMax(root[zj],1,n,seg[x],seg[y]));
}
int main()
{
n=get();
q=get();
for(int i=1;i<=n;++i)
{
w[i]=get();
zj[i]=get();
}
for(int i=1;i<n;++i)
{
int x,y;
x=get();
y=get();
add(x,y);
add(y,x);
}
dfs1(1,0);
seg[0]=seg[1]=top[1]=rev[1]=1;//根结点所在重路径的顶部结点一定还是根结点
dfs2(1,0);
for(int i=1;i<=n;++i)
update(root[zj[i]],1,n,w[i],seg[i]);
char str[20];
while(q--)
{
int x,y;
scanf("%s",str);
x=get();
y=get();
if(str[0]=='C')
{
if(str[1]=='C')
{
remove(root[zj[x]],1,n,seg[x]);
update(root[y],1,n,w[x],seg[x]);
zj[x]=y;
}
else
{
remove(root[zj[x]],1,n,seg[x]);
update(root[zj[x]],1,n,y,seg[x]);
w[x]=y;
}
}
else
{
if(str[1]=='S')
{
Ssum=0;
askSum(x,y,zj[x]);
printf("%d\n",Ssum);
}
else
{
Mmax=0;
askMax(x,y,zj[x]);
printf("%d\n",Mmax);
}
}
}
return 0;
}