DFS序可用于解决一些树上单点修改与查询问题
这样就可以在线处理,无需每一次修改跑一遍最短路或是其他带n的算法
在树中,遇到单点修改和查询的问题,可以考虑dfs+线段树or树状数组维护。
Dfs抽象来说就是将一棵树由树状结构转化为线性结构,一个点到它的所有子树节点占据连续的位置。利用一遍的dfs记录每个点的起始位置(in数组)和结束位置(out数组),点的先后并不取决于存储方式,不变的是每个节点所占据的长度即out[x]-in[x]。
今天做的两道题,套路可以说相对固定,但是会有很多细节问题导致WA或RE,需亲自下手:
Problem Description
百度科技园内有n个零食机,零食机之间通过n−1条路相互连通。每个零食机都有一个值v,表示为小度熊提供零食的价值。
由于零食被频繁的消耗和补充,零食机的价值v会时常发生变化。小度熊只能从编号为0的零食机出发,并且每个零食机至多经过一次。另外,小度熊会对某个零食机的零食有所偏爱,要求路线上必须有那个零食机。
为小度熊规划一个路线,使得路线上的价值总和最大。
Input
输入数据第一行是一个整数T(T≤10),表示有T组测试数据。
对于每组数据,包含两个整数n,m(1≤n,m≤100000),表示有n个零食机,m次操作。
接下来n−1行,每行两个整数x和y(0≤x,y<n),表示编号为x的零食机与编号为y的零食机相连。
接下来一行由n个数组成,表示从编号为0到编号为n−1的零食机的初始价值v(|v|<100000)。
接下来m行,有两种操作:0 x y,表示编号为x的零食机的价值变为y;1 x,表示询问从编号为0的零食机出发,必须经过编号为x零食机的路线中,价值总和的最大值。
本题可能栈溢出,辛苦同学们提交语言选择c++,并在代码的第一行加上:
`#pragma comment(linker, "/STACK:1024000000,1024000000") `
Output
对于每组数据,首先输出一行”Case #?:”,在问号处应填入当前数据的组数,组数从1开始计算。
对于每次询问,输出从编号为0的零食机出发,必须经过编号为x零食机的路线中,价值总和的最大值。
Sample Input
1
6 5
0 1
1 2
0 3
3 4
5 3
7 -5 100 20 -5 -7
1 1
1 3
0 2 -1
1 1
1 5
Sample Output
Case #1:
102
27
2
20
题目概括
给定一棵n个点的有根树,每个点有一个点权。根节点为0,节点标号为0~n-1。
定义最大路径为:从根出发走到某个点,点权和最大的路径。
现在有Q次操作,每种是以下两种之一:
(1).将点x的点权变成v。
(2).求经过某一个点的最大路径的点权和。
题解
线段树。
我们设:
w[x]为节点x的权值
Dis[x]为从根节点到达当前节点的权值
那么,一开始,dis[x]可以通过一遍dfs全部求出来。
如何处理更改和询问呢?那么我们从询问入手。
题目询问的是经过一个点的最大路径的点权和,那么其实就是到达这个节点或者其子孙节点的最大dis[x], 其实就是查询整个子树的max(dis[x])。
那么我们想到了什么?因为在树的dfs序中,同一个子树节点的所对应的序号一定是整个dfs序中的连续的一段!
那么我们就可以把询问转化成“求区间最大值”的问题了。然而同理思考,那么修改其实就是修改整个子树的最大值!对于0 x y, 其实就是子树x的所有节点都增加y-w[x]!那么最大值也增加y-w[x]。
问题就变成了一个询问区间最大值和区间修改(同增或者同减)的问题了。
于是,我们就可以用线段树来维护。
给出一组查错数据:
/*
4
9 5
1 0
1 2
2 3
4 1
5 3
6 5
7 5
7 8
23182 80368 7060 -50161 81799 8841 90480 3016 -4312
1 0
0 8 -438497
0 4 -188568
1 4
0 6 -176104
9 4
0 1
0 2
3 0
3 4
3 5
6 1
7 6
0 8
61060 -24449 -72783 -77927 -33421 80849 8262 -24364 90327
0 2 116045
0 5 404857
1 2
1 5
7 8
1 0
0 2
2 3
4 2
2 5
6 1
16405 -88702 4022 40275 80451 68322 78648
0 3 -39689
1 3
0 3 100112
0 2 109684
1 6
1 2
0 3 -345744
0 3 144465
7 2
0 1
2 0
0 3
1 4
5 0
4 6
-31521 32600 -32746 -67252 40896 94763 99624
0 5 372906
0 5 -118828
ans:
Case #1:
185349
-85018
Case #2:
177105
387990
Case #3:
-19262
6351
226201
Case #4:
*/
POJ3321 Apple Tree
题意概括
有一颗01树,以结点1为树根,一开始所有的结点权值都是1,有两种操作:
1.改变其中一个结点的权值(0变1,1变0)
2.询问子树X的节点权值和。
输入描述
一组数据。
先是一个数n,表示有n个节点。
接下去n-1行,每行表示一条边。
然后一个数m,表示有m个操作。
然后m行,每行一个字母一个数x,如果字母是Q,则是询问;否则是修改。
输出描述
每一个询问一行,表示答案。
题解
题目变成了单点修改和区间sum询问的问题。
单点修改,不用线段树,树状数组就可以了。
第一题:
#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <map>
#include <cstring>
#include <string>
#include <set>
#include <vector>
#define LL long long
using namespace std;
const int maxn=1e5+5;
const LL INF=1e18;
int n,m,k;
LL w[maxn],dis[maxn];
int time,in[maxn],out[maxn],head[maxn];
struct TreeNode{
LL v,add;
int l,r;
}tr[maxn<<2];
struct Edge{
int u,v,next;
}e[maxn];
void addedge(int u,int v){
e[++k]=(Edge){u,v,head[u]};
head[u]=k;
e[++k]=(Edge){v,u,head[v]};
head[v]=k;
}
void pushup(int id){
tr[id].v=max(tr[id<<1].v,tr[id<<1|1].v);
}
void pushdown(int id){
if(tr[id].add){
LL temp=tr[id].add;
tr[id<<1].v+=temp;
tr[id<<1].add+=temp;
tr[id<<1|1].v+=temp;
tr[id<<1|1].add+=temp;
tr[id].add=0;
}
}
void build(int l,int r,int id){
tr[id].l=l; tr[id].r=r; tr[id].add=0;
if(l==r){
tr[id].v=dis[l];
return;
}
int m=(l+r)>>1;
build(l,m,id<<1);
build(m+1,r,id<<1|1);
pushup(id);
}
void update(int L,int R,LL d,int id){
if(tr[id].l>R || tr[id].r<L) return;
if(tr[id].l>=L && tr[id].r<=R){
tr[id].v+=d;
tr[id].add+=d;
return;
}
pushdown(id);
int m=(tr[id].l+tr[id].r)>>1;
update(L,R,d,id<<1);
update(L,R,d,id<<1|1);
pushup(id);
}
LL query(int L,int R,int id){
if(tr[id].l>R || tr[id].r<L) return -INF;
if(tr[id].l>=L && tr[id].r<=R){
return tr[id].v;
}
pushdown(id);
int m=(tr[id].l+tr[id].r)>>1;
return max(query(L,R,id<<1),query(L,R,id<<1|1));
pushup(id);
}
void dfs(int rt,int pre){
in[rt]=++time;
dis[in[rt]]=dis[in[pre]]+w[rt];
for(int i=head[rt];i!=-1;i=e[i].next){
if(e[i].v!=pre) dfs(e[i].v,rt);
}
out[rt]=time;
}
void debug(){
for(int i=0;i<n;i++){
cout<<"in= "<<in[i]<<" out="<<out[i]<<endl;
}
}
int main(){
int op,_,a,b; LL z;cin>>_;
int t=0;
while(_--){
scanf("%d%d",&n,&m);
k=0;
memset(tr,0,sizeof tr);
memset(head,-1,sizeof head);
for(int i=0;i<n-1;i++){
scanf("%d%d",&a,&b);
a++; b++;
addedge(a,b);
}
for(int i=1;i<=n;i++) scanf("%lld",&w[i]);
in[0]=out[0]=time=0;
dfs(1,0); //debug();
build(1,n,1);
printf("Case #%d:\n",++t);
while(m--){
scanf("%d",&op);
if(op==0){
scanf("%d%lld",&a,&z);
a++;
update(in[a],out[a],z-w[a],1);
w[a]=z;
}
else{
scanf("%d",&a);
a++;
printf("%lld\n",query(in[a],out[a],1));
}
}
}
return 0;
}
第二题:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <map>
#include <cstring>
#include <string>
#include <set>
#include <vector>
#define LL long long
using namespace std;
const int maxn=100005;
int n,m,k,time;
bool state[maxn];
int h[maxn],in[maxn],out[maxn],head[maxn],sum[maxn];
struct edge{
int u,v,next;
}e[maxn];
void dfs(int rt,int pre){
in[rt]=++time;
for(int i=head[rt];i;i=e[i].next){
if(e[i].v!=pre) dfs(e[i].v,rt);
}
out[rt]=time;
}
int lowbit(int i){
return i&(-i);
}
void update(int i,int x){
while(i<maxn){
h[i]+=x;
i=i+lowbit(i);
}
}
int query(int i){
int res=0;
while(i>0){
res+=h[i];
i=i-lowbit(i);
}
return res;
}
void addedge(int u,int v){
e[++k]=(edge){u,v,head[u]};
head[u]=k;
e[++k]=(edge){v,u,head[v]};
head[v]=k;
}
int main(){
int a,b;
char op;
cin>>n;
k=0;
memset(head,0,sizeof head);
for(int i=0;i<n-1;i++){
scanf("%d%d",&a,&b);
addedge(a,b);
}
h[0]=0;
for(int i=1;i<=n;i++) {
h[i]=lowbit(i);
state[i]=1;
}
in[0]=out[0]=time=0;
dfs(1,0);
cin>>m;
while(m--){
getchar();
scanf("%c %d",&op,&a);
if(op=='C'){
if(state[a]){state[a]=0;update(in[a],-1);}
else{state[a]=1;update(in[a],1);}
}
else{
printf("%d\n",query(out[a])-query(in[a]-1));
}
}
return 0;
}