DFS序
dfs序比较重要的性质:一棵子树的所有节点在dfs序里是连续一段,主要就是利用这个性质来解题。 跑出dfs序,然后数据结构维护
经典问题
T1单点修改,子树查询
对某个点X权值加上一个数W,查询某个子树X里所有点权值和 解:列出dfs序,实现修改一个数,查询一段序列的和,显然这个序列可以用树状数组维护。 代码:
# include <iostream>
using namespace std;
const int MAXN = 100010;
struct edge{int t,w,next;}edges[MAXN<<1];
int head[MAXN];
int top;
void add(int f,int t,int w) {
edges[++top].next = head[f];
edges[top].t = t;
edges[top].w = w;
head[f] = top;
}
int tot;int dfn;
int a[MAXN];int st[MAXN];int ed[MAXN];
inline int lowbit(int x){return x&-x;}
void modify(int x,int v){
while(x<=tot){
a[x]+=v;
x+=lowbit(x);
}
}
int query(int x){int ans = 0;while(x>=1){ans+=a[x];x-=lowbit(x);}return ans;}
bool vis[MAXN];
void dfs(int x){
vis[x] = true;
st[x] = ++dfn;
for(int i = head[x];i;i = edges[i].next){
int t = edges[i].t;
if(vis[t])continue;
dfs(t);
}
ed[x] = dfn;
}
int main(){
ios::sync_with_stdio(false);
int n;cin>>n;
int f,t;
for(int i = 1;i<n;i++){cin>>f>>t;add(f,t,1),add(t,f,1);}
int Q;cin>>Q;
dfs(1);tot = dfn;
char op;int x;
for(int i = 1;i<=n;i++)
modify(i,1);
while(Q--){
cin>>op>>x;
if(op=='Q'){
cout<<query(ed[x])-query(st[x]-1)<<endl;
}else{
if(query(st[x])-query(st[x]-1)==1)modify(st[x],-1);else modify(st[x],1);
}
}
}
T2树上路径修改,单点查询
• 对X到Y的最短路上所有点权值加上一个数W,查询某个点的权值。 对x—》y的路径修改等价于:
- x->root 加v
- y->root 加v
- lca(x,y) -v
- fa[lca(x,y)] -v
为什么呢?因为是统计x到y的最短路加v,lca(x,y)也在路径上,所以需要加v,但是因为x和y分别到root 的时候,都加了个v,lca被加了2次,所以需要减一次,而fa[lca(x,y)]不在路径上,所以需要减一次(前面lca(x,y))已经减了一次了。然后求lca,在树状数组维护就行了。这种问题,要把它分成几个问题来理解。树状数组是实现单点查询/修改的,其他是满足修改的方法的,不要绞一起来思考。
# include <cstdio>
int read(){
int x = 0,f = 1;
static char c = getchar();
while(c<'0'||c>'9'){ if(c=='-')f = -1;c = getchar(); }
while(c>='0'&&c<='9'){ x = (x<<1)+(x<<3)+c-'0';c = getchar(); }
return x*f;
}
# define in read()
const int MAXN = 100010;
struct edge{int t,w,next;}edges[MAXN<<1];
int head[MAXN];
int top;
void add(int f,int t,int w) {
edges[++top].next = head[f];
edges[top].t = t;
edges[top].w = w;
head[f] = top;
}
inline int swap(int &x,int &y){x = x^y;y = y ^x;x = x^y;}
int fa[MAXN][20];
int dep[MAXN];
int st[MAXN],ed[MAXN];
bool vis[MAXN];
int a[MAXN];
int n;
int dfn;
inline int lowbit(int x){return x&-x;}
int query(int x){
int ans = 0;
while(x>0){ans+=a[x];x-=lowbit(x);}
return ans;
}
int modify(int x,int v){
while(x<=n){
a[x]+=v;
x+=lowbit(x);
}
}
void dfs(int x,int f){
vis[x] = true;
fa[x][0] = f;
dep[x] = dep[f]+1;
st[x] = ++dfn;
for(int i = 1;i<=19;i++)fa[x][i] = fa[fa[x][i-1]][i-1];
for(int i = head[x];i;i = edges[i].next){
int t = edges[i].t;
if(vis[t])continue;
dfs(t,x);
}
ed[x] = dfn;
}
int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
for(int i = 19;i>=0;i--)if(dep[fa[x][i]]>=dep[y])x = fa[x][i];
if(x==y)return x;
for(int i = 19;i>=0;i--){
if(fa[x][i]!=fa[y][i]){
x = fa[x][i],y = fa[y][i];
}
}
return fa[x][0];
}
int main(){
n = read();
int f,t;
for(int i = 1;i<n;i++){
f = in;t =in;add(f,t,1);add(t,f,1);
}
int Q;Q = in;
int op,x,y,v;
dfs(1,0);
while(Q--){
op = in;
if(op==1){
x = in;y = in;v= in;
int lc = lca(x,y);
modify(st[x],v);modify(st[y],v);
modify(st[lc],-v);
if(lc!=1)modify(st[fa[lc][0]],-v);
}else{
x = in;
printf("%d \n",query(ed[x])-query(st[x]-1));
}
}
return 0;
}
T3树上路径修改,子树查询2231
从X到Y的点加上或者减去一个W,求某个点子树内的所有点的权值和. 解答:我们考虑对于每个修改的影响,如果x是y的子树,那么对y这棵树的影响是$w(x)*(dep[x]-dep[y]+1)$,那么我们把它拆开$w(x)*(dep[x]+1]-dep[y]*w(x))$,然后考虑T2的差分的做法,那么我们直接维护到根,然后减去(lca到根+lca的父节点到根)就行了(参见T2的理解或者做一下T4)。两个树状数组维护$w(x)$和$w(x)*(dep[x]-1)$就行了。代码如下:
# include <bits/stdc++.h>
using namespace std;
struct IO
{
streambuf *ib,*ob;
int buf[50];
inline void init()
{
ios::sync_with_stdio(false);
cin.tie(NULL);cout.tie(NULL);
ib=cin.rdbuf();ob=cout.rdbuf();
}
inline int read()
{
char ch=ib->sbumpc();int i=0,f=1;
while(!isdigit(ch)){if(ch=='-')f=-1;ch=ib->sbumpc();}
while(isdigit(ch)){i=(i<<1)+(i<<3)+ch-'0';ch=ib->sbumpc();}
return i*f;
}
inline void W(int x)
{
if(!x){ob->sputc('0');return;}
if(x<0){ob->sputc('-');x=-x;}
while(x){buf[++buf[0]]=x%10,x/=10;}
while(buf[0])ob->sputc(buf[buf[0]--]+'0');
}
}io;
const int MAXN = 100010;
struct edge{int t,w,next;}edges[MAXN<<1];
int head[MAXN];
int top;
void add(int f,int t,int w) {
edges[++top].next = head[f];
edges[top].t = t;
edges[top].w = w;
head[f] = top;
}
int fa[MAXN][20];
int dep[MAXN];
int st[MAXN],ed[MAXN];
bool vis[MAXN];
int a[MAXN];
int n;
int dfn;
inline int lowbit(int x){return x&-x;}
class tree{//I LOVE OOP
private:
int a[MAXN];
public:
int query(int x){
int ans = 0;
while(x>0){ans+=a[x];x-=lowbit(x);}
return ans;
}
int modify(int x,int v){
while(x<=n){
a[x]+=v;
x+=lowbit(x);
}
}
};
void dfs(int x,int f){
vis[x] = true;
fa[x][0] = f;
dep[x] = dep[f]+1;
st[x] = ++dfn;
for(int i = 1;i<=19;i++)fa[x][i] = fa[fa[x][i-1]][i-1];
for(int i = head[x];i;i = edges[i].next){
int t = edges[i].t;
if(vis[t])continue;
dfs(t,x);
}
ed[x] = dfn;
}
int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
int delta = dep[x]-dep[y];
for(int i = 19;i>=0;i--)if(dep[fa[x][i]]>=dep[y])x = fa[x][i];
if(x==y)return x;
for(int i = 19;i>=0;i--){
if(fa[x][i]!=fa[y][i]){
x = fa[x][i],y = fa[y][i];
}
}
return fa[x][0];
}
tree t1,t2;
int main(){
io.init();
n = io.read();
int f,t;
for(int i = 1;i<n;i++){
f = io.read(),t = io.read();add(f,t,1);add(t,f,1);
}
int Q;Q = io.read();
int op,x,y,z;
dfs(1,0);
while(Q--){
op = io.read();
if(op==1){
x = io.read(),y = io.read(), z = io.read();
int lc = lca(x,y);
t1.modify(st[x],z);
t1.modify(st[y],z);
t2.modify(st[x],z*(dep[x]+1));
t2.modify(st[y],z*(dep[y]+1));
t1.modify(st[lc],-z);
t2.modify(st[lc],-z*(dep[lc]+1));
if(lc!=1){
t1.modify(st[fa[lc][0]],-z);
t2.modify(st[fa[lc][0]],-z*(dep[fa[lc][0]]+1));
}
}else{
x = io.read();
printf("%d\n",t2.query(ed[x])-t2.query(st[x]-1)-dep[x]*(t1.query(ed[x])-t1.query(st[x]-1)));
}
}
return 0;
}
T4单点修改,路径询问2232
最简单的一个问题,直接x到根+y到根-lca到根-lca的父节点到根的点权和就完事了。树状数组超级好维护
# include <bits/stdc++.h>
using namespace std;
struct IO
{
streambuf *ib,*ob;
int buf[50];
inline void init()
{
ios::sync_with_stdio(false);
cin.tie(NULL);cout.tie(NULL);
ib=cin.rdbuf();ob=cout.rdbuf();
}
inline int read()
{
char ch=ib->sbumpc();int i=0,f=1;
while(!isdigit(ch)){if(ch=='-')f=-1;ch=ib->sbumpc();}
while(isdigit(ch)){i=(i<<1)+(i<<3)+ch-'0';ch=ib->sbumpc();}
return i*f;
}
inline void W(int x)
{
if(!x){ob->sputc('0');return;}
if(x<0){ob->sputc('-');x=-x;}
while(x){buf[++buf[0]]=x%10,x/=10;}
while(buf[0])ob->sputc(buf[buf[0]--]+'0');
}
}io;
const int MAXN = 100010;
struct edge{int t,w,next;}edges[MAXN<<1];
int head[MAXN];
int top;
void add(int f,int t,int w) {
edges[++top].next = head[f];
edges[top].t = t;
edges[top].w = w;
head[f] = top;
}
int fa[MAXN][20];
int dep[MAXN];
int st[MAXN],ed[MAXN];
bool vis[MAXN];
int a[MAXN];
int n;
int dfn;
inline int lowbit(int x){return x&-x;}
class tree{//I LOVE OOP
private:
int a[MAXN];
public:
int query(int x){
int ans = 0;
while(x>0){ans+=a[x];x-=lowbit(x);}
return ans;
}
int modify(int x,int v){
while(x<=n){
a[x]+=v;
x+=lowbit(x);
}
}
};
void dfs(int x,int f){
vis[x] = true;
fa[x][0] = f;
dep[x] = dep[f]+1;
st[x] = ++dfn;
for(int i = 1;i<=19;i++)fa[x][i] = fa[fa[x][i-1]][i-1];
for(int i = head[x];i;i = edges[i].next){
int t = edges[i].t;
if(vis[t])continue;
dfs(t,x);
}
ed[x] = dfn;
}
int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
int delta = dep[x]-dep[y];
for(int i = 19;i>=0;i--)if(dep[fa[x][i]]>=dep[y])x = fa[x][i];
if(x==y)return x;
for(int i = 19;i>=0;i--){
if(fa[x][i]!=fa[y][i]){
x = fa[x][i],y = fa[y][i];
}
}
return fa[x][0];
}
tree t1;
int wei[MAXN];
int main(){
io.init();
n = io.read();
int f,t;
for(int i = 1;i<=n;i++)wei[i] = io.read();
for(int i = 1;i<n;i++){
f = io.read(),t = io.read();add(f,t,1);add(t,f,1);
}
int Q;Q = io.read();
int op,x,y,z;
dfs(1,0);
for(int i = 1;i<=n;i++)t1.modify(st[i],wei[i]),t1.modify(ed[i]+1,-wei[i]);
while(Q--){
op = io.read();
if(op==1){
x = io.read();z = io.read();
t1.modify(st[x],z);t1.modify(ed[x]+1,-z);
}else{
x = io.read();y = io.read();
int lc = lca(x,y);
int ans = t1.query(st[x])+t1.query(st[y])-t1.query(st[fa[lc][0]]) - t1.query(st[lc]);
printf("%d\n",ans);
}
}
return 0;
}
T5T6T7
更简单了,不过涉及到一些树状数组不好维护的东西超级树状数组,就直接换线段树,或者其他数据结构就完事了。
题型五:子树修改,单点查询
问题六:子树修改,子树查询
问题七:子树修改,路径查询