题目描述
一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w。
我们将以下面的形式来要求你对这棵树完成一些操作:
I. CHANGE u t : 把结点u的权值改为t
II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值
III. QSUM u v: 询问从点u到点v的路径上的节点的权值和
注意:从点u到点v的路径上的节点包括u和v本身
对于100%的数据,保证1<=n<=30000,0<=q<=200000;中途操作中保证每个节点的权值w在-30000到30000之间。
一眼树链剖分
显然,这是一道关于维护树中路径的题目,一眼树链剖分。本弱并没有打树链剖分解法。
作死的动态树
强行把这道题打成动态树也是可以的。。。
代买如下:
#include<cstdio>
#include<algorithm>
#include<stack>
#include<cstring>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=30000+10;
stack<int> sta;
struct dong{
int x,y;
};
dong e[maxn];
int tree[maxn][2],key[maxn],father[maxn],num[maxn],sum[maxn],pp[maxn];
int i,j,k,l,t,n,m;
bool bz[maxn];
char ch;
int pd(int x){
if (tree[father[x]][0]==x) return 0;else return 1;
}
void update(int x){
num[x]=max(key[x],max(num[tree[x][0]],num[tree[x][1]]));
sum[x]=sum[tree[x][0]]+sum[tree[x][1]]+key[x];
}
void rotate(int x){
int y=father[x],z=pd(x);
tree[y][z]=tree[x][1-z];
if (tree[x][1-z]) father[tree[y][z]]=y;
father[x]=father[y];
if (father[y]) tree[father[y]][pd(y)]=x;
father[y]=x;
tree[x][1-z]=y;
update(y);
update(x);
if (pp[y]) pp[x]=pp[y],pp[y]=0;
}
void clear(int x){
if (bz[x]){
bz[x]=0;
if (tree[x][0]) bz[tree[x][0]]^=1;
if (tree[x][1]) bz[tree[x][1]]^=1;
swap(tree[x][0],tree[x][1]);
}
}
void romove(int x,int y){
while (x!=y){
sta.push(x);
x=father[x];
}
while (!sta.empty()){
clear(sta.top());
sta.pop();
}
}
void splay(int x,int y){
romove(x,y);
while (father[x]!=y){
if (father[father[x]]!=y)
if (pd(x)==pd(father[x])) rotate(x);else rotate(father[x]);
rotate(x);
}
}
void access(int x){
int y;
splay(x,0);
father[tree[x][1]]=0;
if (tree[x][1]) pp[tree[x][1]]=x;
tree[x][1]=0;
update(x);
while (pp[x]!=0){
y=pp[x];
splay(y,0);
father[tree[y][1]]=0;
if (tree[y][1]) pp[tree[y][1]]=y;
tree[y][1]=x;
father[x]=y;
pp[x]=0;
update(y);
splay(x,0);
}
}
void makeroot(int x){
access(x);
splay(x,0);
bz[x]^=1;
}
void link(int x,int y){
makeroot(x);
access(y);
splay(x,0);
pp[x]=y;
access(x);
}
int read(){
int x=0,f=1;
char ch=getchar();
while (ch<'0'||ch>'9'){
if (ch=='-') f=-1;
ch=getchar();
}
while (ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
char get(){
char ch=getchar();
while (ch<'A'||ch>'Z') ch=getchar();
return ch;
}
int main(){
num[0]=-10000000;sum[0]=0;
scanf("%d",&n);
fo(i,1,n-1) scanf("%d%d",&e[i].x,&e[i].y);
fo(i,1,n){
scanf("%d",&key[i]);
num[i]=sum[i]=key[i];
}
fo(i,1,n-1) link(e[i].x,e[i].y);
scanf("%d",&m);
while (m--){
if (get()=='C'){
j=read();k=read();
splay(j,0);
key[j]=k;
update(j);
}
else{
ch=get();
j=read();k=read();
makeroot(j);
access(k);
splay(k,0);
if (ch=='M') printf("%d\n",num[k]);else printf("%d\n",sum[k]);
}
}
}
脑洞大开
这道题已经被解决了,那我们来脑洞大开一下:
现在本题去掉修改操作,数据范围都多加个0,该怎么做?
我的想法是离线求出每个询问的lca,然后把询问挂在那。
然后递归处理,对于x,先递归处理x的每棵子树,然后现在我们有两颗线段树,第一颗线段树维护dfn序的区间和,第二个维护区间max。那么对于点i,现在保存的就是其到其最远被处理完的祖先的距离和与距离max,在线段树的dfn[i]位置上。
现在扫描到x的一个子树,递归处理其后,对其对应的线段树区间做修改(第一颗打add标记,第二颗打max标记)。
如果x上有询问,显然可以解决。
复杂度n log n。
然而显然我们可以用并查集维护而不是线段树就变成近乎o(n)了。