作者:weeping
出处:https://www.cnblogs.com/weeping/p/6847112.html
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
注:由于未找到相关题目,我自己敲的代码不确定正确性,我只自己测了自己小样例。(事后加上)
定义:dfs序:每个节点在dfs深度优先遍历中的进出栈的时间序列
定义两个数组,in[x],ou[x]。dfs从根结点开始,每个结点分别记录两个信息:in[x],ou[x],in[x]为dfs进入结点x时的时间戳,ou[x]为dfs离开结点x时的时间戳 ,这样子 in[x] --- ou[x] 区间表示以 x 为根的子树
void dfs(int u,int fa){
in[u]=++time;
num[time]=u;
dep[u]=dep[fa]+1;
for(int i=0;i<E[u].size();i++){
int v=E[u][i];
if(v==fa)continue;
dfs(v,u);
}
ou[u]=time;
}
性质:dfs序可以把一棵树区间化,即可以求出每个节点的管辖区间。
对于一棵树的dfs序而言,同一棵子树所对应的一定是dfs序中连续的一段。
dfs序的七个基本问题:
1.点修改,子树和查询
在dfs序中,子树处于一个连续区间中。所以这题可以转化为:点修改,区间查询。用树状数组或线段树即可。
#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
const int INF=1e9;
const int mod=1e9+7;
const int N=1e5+5;
#define LL long long
int in[N],ou[N],num[N],time;
vector<vector<int> >E(N);//POJ直接用vector超时
void dfs(int u,int fa){
in[u]=++time;
num[time]=u;
int v;
for(int i=0;i<E[u].size();i++)
if((v=E[u][i])!=fa)
dfs(v,u);
ou[u]=time;
}
int val[N],n;
inline int lowbit(int x){
return x&(-x);
}
void add(int x,int v){
for(int i=x;i<=n;i+=lowbit(i))
val[i]+=v;
}
int sum(int x){
int ans=0;
for(int i=x;i;i-=lowbit(i))
ans+=val[i];
return ans;
}
bool vis[N];
int main(){
cin>>n;
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
E[u].push_back(v);
E[v].push_back(u);
}
dfs(1,0);
for(int i=1;i<=n;i++)
add(i,1),vis[i]=1;
int q;cin>>q;
while(q--){
char ch;
int x;
getchar();
scanf("%c %d",&ch,&x);
if(ch=='Q')
printf("%d\n",sum(ou[x])-sum(in[x]-1));
else{
if(vis[x]==1)
add(in[x],-1);
else
add(in[x],1);
vis[x]=!vis[x];
}
}
return 0;
}
2.树链修改,单点查询
将一条树链x,y上的所有点的权值加v。这个问题可以等价为:
1).x到根节点的链上所有节点权值加v。
2).y到根节点的链上所有节点权值加v。
3).lca(x,y)到根节点的链上所有节点权值和减v。
4).fa(lca(x,y))到根节点的链上所有节点权值和减v。
上面四个操作可以归结为:节点x到根节点链上所有节点的权值加减v。修改节点x权值,当且仅当y是x的祖先节点时,x对y的值有贡献。
所以节点y的权值可以转化为节点y的子树节点贡献和。从贡献和的角度想:这就是点修改,区间和查询问题。
修改树链x,y等价于add(l[x],v),add(l[y],v),add(l[lca(x,y)],-v),add(l[fa(lca(x,y))],-v)。
查询:get_sum(r[x])-get_sum(l[x]-1)
用树状数组或线段树即可。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N=1e5+5;
//树状数组部分
int val[N],n;
inline int lowbit(int x){
return x&(-x);
}
void add(int x,int v){
if(x==0)return;
for(int i=x;i<=n;i+=lowbit(i))
val[i]+=v;
}
int sum(int x){
int ans=0;
for(int i=x;i;i-=lowbit(i))
ans+=val[i];
return ans;
}
//DFS序部分 + LCA
int in[N],ou[N],num[N],tim;
int dep[N],f[N][22];
vector<vector<int>>E(N);
void dfs(int u,int fa){
in[u]=++tim;
num[tim]=u;
//初始化f数组
dep[u]=dep[fa]+1;
f[u][0]=fa;
for(int i=1;i<=20;i++)
f[u][i]=f[f[u][i-1]][i-1];
for(int i=0;i<E[u].size();i++){
int v=E[u][i];
if(v==fa)continue;
dfs(v,u);
}
ou[u]=tim;
}
int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
int d=dep[x]-dep[y];
for(int i=0;i<20;i++)
if((1<<i)&d)
x=f[x][i];
if(x==y)return x;
for(int i=20;i>=0;i--)
if(f[x][i]!=f[y][i]){
x=f[x][i];
y=f[y][i];
}
return f[x][0];
}
int main(){
cin>>n;
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
E[u].push_back(v);
E[v].push_back(u);
}
dfs(1,0); //假设以1为根
int q;cin>>q;
while(q--){ //树链修改,单点求值
char ch;
int x,y,v;
getchar();
scanf("%c%d",&ch,&x);
if(ch=='Q')
printf("%d\n",sum(ou[x])-sum(in[x]-1));
else if(ch=='C'){
scanf("%d%d",&y,&v);
add(in[x],v);
add(in[y],v);
int fxy=lca(x,y);
add(in[fxy],-v);
add(in[f[fxy][0]],-v);
}
}
return 0;
}
/*给出一组数据
7
1 3
1 4
3 5
3 7
4 6
4 2
7
C 5 7 2
Q 3
C 5 2 3
Q 3
Q 4
Q 5
Q 1
*/
3.树链修改,子树和查询
树链修改部分同上一问题。下面考虑子树和查询问题:前一问是从贡献的角度想,子树和同理。
对于节点y其到根节点的权值和,考虑其子节点x的贡献:w[x]*(deep[x]-deep[y]+1) = w[x]*(deep[x]+1)-w[x]*deep[y]
所以节点y的子树和为:
ps:公式中的v[i]为手误,应为w[i]。
所以用两个树状数组或线段树即可:
第一个维护∑w[i]*(deep[i]+1):支持操作单点修改,区间和查询。(这也就是问题2)
第二个维护∑ w[i]:支持操作单点修改,区间查询。(这其实也是问题2)
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N=1e5+5;
//树状数组部分
int val1[N],val2[N],n;
inline int lowbit(int x){
return x&(-x);
}
void add(int x,int v,int *val){
if(x==0)return;
for(int i=x;i<=n;i+=lowbit(i))
val[i]+=v;
}
int sum(int x,int *val){
int ans=0;
for(int i=x;i;i-=lowbit(i))
ans+=val[i];
return ans;
}
//DFS序部分 + LCA
int in[N],ou[N],num[N],tim;
int dep[N],f[N][22];
vector<vector<int>>E(N);
void dfs(int u,int fa){
in[u]=++tim;
num[tim]=u;
//初始化f数组
dep[u]=dep[fa]+1;
f[u][0]=fa;
for(int i=1;i<=20;i++)
f[u][i]=f[f[u][i-1]][i-1];
for(int i=0;i<E[u].size();i++){
int v=E[u][i];
if(v==fa)continue;
dfs(v,u);
}
ou[u]=tim;
}
int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
int d=dep[x]-dep[y];
for(int i=0;i<20;i++)
if((1<<i)&d)
x=f[x][i];
if(x==y)return x;
for(int i=20;i>=0;i--)
if(f[x][i]!=f[y][i]){
x=f[x][i];
y=f[y][i];
}
return f[x][0];
}
int main(){
cin>>n;
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
E[u].push_back(v);
E[v].push_back(u);
}
dfs(1,0); //假设以1为根
int q;cin>>q;
while(q--){ //树链修改,单点求值
char ch;
int x,y,v;
getchar();
scanf("%c%d",&ch,&x);
if(ch=='Q'){
LL ans=sum(ou[x],val1)-sum(in[x]-1,val1);
ans-=dep[x]*(sum(ou[x],val2)-sum(in[x]-1,val2));
printf("%lld\n",ans);
}
else if(ch=='C'){
scanf("%d%d",&y,&v);
int fxy=lca(x,y);
//Val1
add(in[x],v*(dep[x]+1),val1);
add(in[y],v*(dep[y]+1),val1);
add(in[fxy],-v*(dep[fxy]+1),val1);
add(in[f[fxy][0]],-v*(dep[f[fxy][0]]+1),val1);
//Val2
add(in[x],v,val2);
add(in[y],v,val2);
add(in[fxy],-v,val2);
add(in[f[fxy][0]],-v,val2);
}
}
return 0;
}
/*给出一组数据
7
1 3
1 4
3 5
3 7
4 6
4 2
7
C 5 7 2
Q 3
C 5 2 3
Q 3
Q 4
Q 5
Q 1
*/
4.单点更新,树链和查询
树链和查询与树链修改类似,树链和(x,y)等于下面四个部分和相加:
1).x到根节点的链上所有节点权值加。
2).y到根节点的链上所有节点权值加。
3).lca(x,y)到根节点的链上所有节点权值和的-1倍。
4).fa(lca(x,y))到根节点的链上所有节点权值和的-1倍。
所以问题转化为:查询点x到根节点的链上的所有节点权值和。
修改节点x权值,当且仅当y是x的子孙节点时,x对y的值有贡献。
差分前缀和,y的权值等于dfs中[1,l[y]]的区间和。(不会树状数组进行该操作的可以参考:https://www.cnblogs.com/kickit/p/9172189.html
单点修改:add(l[x],v),add(r[x]+1,-v);
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N=1e5+5;
//树状数组部分
int val[N],n;
inline int lowbit(int x){
return x&(-x);
}
void add(int x,int v,int *val){
if(x==0)return;
for(int i=x;i<=n;i+=lowbit(i))
val[i]+=v;
}
int sum(int x,int *val){
int ans=0;
for(int i=x;i;i-=lowbit(i))
ans+=val[i];
return ans;
}
//DFS序部分 + LCA
int in[N],ou[N],num[N],tim;
int dep[N],f[N][22];
vector<vector<int>>E(N);
void dfs(int u,int fa){
in[u]=++tim;
num[tim]=u;
//初始化f数组
dep[u]=dep[fa]+1;
f[u][0]=fa;
for(int i=1;i<=20;i++)
f[u][i]=f[f[u][i-1]][i-1];
for(int i=0;i<E[u].size();i++){
int v=E[u][i];
if(v==fa)continue;
dfs(v,u);
}
ou[u]=tim;
}
int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
int d=dep[x]-dep[y];
for(int i=0;i<20;i++)
if((1<<i)&d)
x=f[x][i];
if(x==y)return x;
for(int i=20;i>=0;i--)
if(f[x][i]!=f[y][i]){
x=f[x][i];
y=f[y][i];
}
return f[x][0];
}
int main(){
cin>>n;
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
E[u].push_back(v);
E[v].push_back(u);
}
dfs(1,0); //假设以1为根
int q;cin>>q;
while(q--){ //树链修改,单点求值
char ch;
int x,y,v;
getchar();
scanf("%c%d",&ch,&x);
if(ch=='Q'){
scanf("%d",&y);
LL ans =sum(in[x],val);
ans+=sum(in[y],val);
int fxy=lca(x,y);
ans-=sum(in[fxy],val);
ans-=sum(in[f[fxy][0]],val);
printf("%lld\n",ans);
}
else if(ch=='C'){
scanf("%d",&v);
add(in[x],v,val);
add(ou[x]+1,-v,val);
}
}
return 0;
}
/**给出一组数据
7
1 3
1 4
3 5
3 7
4 6
4 2
8
Q 1 2
C 3 2
Q 5 7
C 4 1
Q 5 2
C 1 1
Q 7 2
Q 1 1
*/
5.子树修改,单点查询
修改节点x的子树权值,在dfs序上就是区间修改,单点权值查询就是单点查询。
区间修改,单点查询问题:树状数组或线段树即可;
(不会树状数组进行该操作的可以参考:https://www.cnblogs.com/kickit/p/9172189.html
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N=1e5+5;
//树状数组部分
int val[N],n;
inline int lowbit(int x){
return x&(-x);
}
void add(int x,int v,int *val){
if(x==0)return;
for(int i=x;i<=n;i+=lowbit(i))
val[i]+=v;
}
int sum(int x,int *val){
int ans=0;
for(int i=x;i;i-=lowbit(i))
ans+=val[i];
return ans;
}
//DFS序部分 + LCA
int in[N],ou[N],num[N],tim;
int dep[N],f[N][22];
vector<vector<int>>E(N);
void dfs(int u,int fa){
in[u]=++tim;
num[tim]=u;
//初始化f数组
dep[u]=dep[fa]+1;
f[u][0]=fa;
for(int i=1;i<=20;i++)
f[u][i]=f[f[u][i-1]][i-1];
for(int i=0;i<E[u].size();i++){
int v=E[u][i];
if(v==fa)continue;
dfs(v,u);
}
ou[u]=tim;
}
int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
int d=dep[x]-dep[y];
for(int i=0;i<20;i++)
if((1<<i)&d)
x=f[x][i];
if(x==y)return x;
for(int i=20;i>=0;i--)
if(f[x][i]!=f[y][i]){
x=f[x][i];
y=f[y][i];
}
return f[x][0];
}
int main(){
cin>>n;
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
E[u].push_back(v);
E[v].push_back(u);
}
dfs(1,0); //假设以1为根
int q;cin>>q;
while(q--){ //子树修改,单点求值
char ch;
int x,y,v;
getchar();
scanf("%c%d",&ch,&x);
if(ch=='Q'){
LL ans = sum(in[x],val);
printf("%lld\n",ans);
}
else if(ch=='C'){
scanf("%d",&v);
add(in[x],v,val);
add(ou[x]+1,-v,val);
}
}
return 0;
}
/**给出一组数据
7
1 3
1 4
3 5
3 7
4 6
4 2
8
Q 1
C 3 2
Q 5
C 1 3
Q 7
Q 1
Q 3
Q 4
*/
6.子树修改,子树和查询
题目等价与区间修改,区间查询问题。用树状数组或线段树即可。
(不会树状数组进行该操作的可以参考:https://www.cnblogs.com/kickit/p/9172189.html
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N=1e5+5;
//树状数组部分
int val1[N],val2[N],n;
inline int lowbit(int x){
return x&(-x);
}
void add(int x,int v,int *val){
if(x==0)return;
for(int i=x;i<=n;i+=lowbit(i))
val[i]+=v;
}
int sum(int x,int *val){
int ans=0;
for(int i=x;i;i-=lowbit(i))
ans+=val[i];
return ans;
}
//DFS序部分 + LCA
int in[N],ou[N],num[N],tim;
int dep[N],f[N][22];
vector<vector<int>>E(N);
void dfs(int u,int fa){
in[u]=++tim;
num[tim]=u;
//初始化f数组
dep[u]=dep[fa]+1;
f[u][0]=fa;
for(int i=1;i<=20;i++)
f[u][i]=f[f[u][i-1]][i-1];
for(int i=0;i<E[u].size();i++){
int v=E[u][i];
if(v==fa)continue;
dfs(v,u);
}
ou[u]=tim;
}
int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
int d=dep[x]-dep[y];
for(int i=0;i<20;i++)
if((1<<i)&d)
x=f[x][i];
if(x==y)return x;
for(int i=20;i>=0;i--)
if(f[x][i]!=f[y][i]){
x=f[x][i];
y=f[y][i];
}
return f[x][0];
}
int main(){
cin>>n;
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
E[u].push_back(v);
E[v].push_back(u);
}
dfs(1,0); //假设以1为根
int q;cin>>q;
while(q--){ //子树修改,子树和查询
char ch;
int x,y,v;
getchar();
scanf("%c%d",&ch,&x);
if(ch=='Q'){
LL ans = (ou[x]+1)*sum(ou[x],val1) - sum(ou[x],val2);
ans-= (in[x])*sum(in[x]-1,val1) - sum(in[x]-1,val2);
printf("%lld\n",ans);
}
else if(ch=='C'){
scanf("%d",&v);
add(in[x],v,val1);
add(ou[x]+1,-v,val1);
add(in[x],v*in[x],val2);
add(ou[x]+1,-v*(ou[x]+1),val2);
}
}
return 0;
}
/**给出一组数据
7
1 3
1 4
3 5
3 7
4 6
4 2
9
Q 1
C 3 2
Q 3
C 1 3
Q 7
Q 1
Q 3
Q 4
Q 2
*/
7.子树修改,树链查询
树链查询同上,等价为根节点到y节点的链上所有节点和问题。
修改节点x的子树权值,当且仅当y是x的子孙节点时(或y等于x),x对y的值有贡献。
x对根节点到y节点的链上所有节点和的贡献为:w[x]*(deep[y]-deep[x]+1)=w[x]*deep[y]+w[x]*(1-deep[x])
同问题三,用两个树状数组或线段树即可。
(不会树状数组进行该操作的可以参考:https://blog.csdn.net/qq_40695203/article/details/101075768
由于本人数据结构太差,我使用四个树状数组维护,前两个搞w[x]*deep[y],后两个搞w[x]*(1-deep[x])
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N=1e5+5;
//树状数组部分
int val1[N],val2[N],val3[N],val4[N],s[N],n;//si是 1--i的dep的和 ,
inline int lowbit(int x){
return x&(-x);
}
void add(int x,int v,int *val){
if(x==0)return;
for(int i=x;i<=n;i+=lowbit(i))
val[i]+=v;
}
int sum(int x,int *val){
if(x<=0)return 0;
int ans=0;
for(int i=x;i;i-=lowbit(i))
ans+=val[i];
return ans;
}
//DFS序部分 + LCA
int in[N],ou[N],num[N],tim;
int dep[N],f[N][22];
vector<vector<int>>E(N);
void dfs(int u,int fa){
in[u]=++tim;
num[tim]=u;
//初始化f数组
if(u)
dep[u]=dep[fa]+1;
f[u][0]=fa;
for(int i=1;i<=20;i++)
f[u][i]=f[f[u][i-1]][i-1];
for(int i=0;i<E[u].size();i++){
int v=E[u][i];
if(v==fa)continue;
dfs(v,u);
}
ou[u]=tim;
}
int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
int d=dep[x]-dep[y];
for(int i=0;i<20;i++)
if((1<<i)&d)
x=f[x][i];
if(x==y)return x;
for(int i=20;i>=0;i--)
if(f[x][i]!=f[y][i]){
x=f[x][i];
y=f[y][i];
}
return f[x][0];
}
inline int get(int id){
if(id<=0)return 0;
return s[id]*sum(id,val1) - sum(id,val2) + (id*sum(id,val3) - sum(id,val4));
}
int main(){
cin>>n;
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
E[u].push_back(v);
E[v].push_back(u);
}
dfs(1,0); //假设以1为根
for(int i=1;i<=n;i++) //处理前缀和s数组
s[i]=dep[num[i]]+s[i-1];
for(int i=1;i<=n;i++)
printf("%d ",dep[num[i]]);puts("");
for(int i=1;i<=n;i++)
printf("%d ",s[i]);puts("");
int q;cin>>q;
while(q--){ //
int x,y,v;
getchar();
scanf("%c%d",&ch,&x);
if(ch=='Q'){
scanf("%d",&y);
LL ans = get(in[x])-get(in[x]-1);
ans+= get(in[y])-get(in[y]-1);
int fxy= lca(x,y);
ans-= get(in[fxy])-get(in[fxy]-1);
ans-= get(in[f[fxy][0]])-get(in[f[fxy][0]]-1);
printf("%lld\n",ans);
}
else if(ch=='C'){
scanf("%d",&v);
add(in[x],v,val1);
add(ou[x]+1,-v,val1);
add(in[x],v*s[in[x]-1],val2);
add(ou[x]+1,-v*s[ou[x]],val2);
int v1=v*(1-dep[x]);
add(in[x],v1,val3);
add(ou[x]+1,-v1,val3);
add(in[x],v1*(in[x]-1),val4);
add(ou[x]+1,-v1*ou[x], val4);
}
}
return 0;
}
/**给出一组数据
7
1 3
1 4
3 5
3 7
4 6
4 2
9
C 3 2
Q 5 3
Q 7 5
C 1 3
Q 3 1
Q 1 1
Q 4 5
Q 5 7
Q 5 2
*/