Escriba en los métodos de entrada frontal y almacenamiento de imágenes utilizados en este artículo
Método de entrada:
Los dos números ny raíz en la primera línea indican que el árbol tiene un total de n nodos, de los cuales el número raíz es el nodo raíz
Siguiente n-1 líneas, dos enteros ab por línea, que indican que el nodo a está conectado al nodo b
const int MAXN=1e4+50;
int dfs_order[MAXN];
int euler_order1[MAXN];
int euler_order2[MAXN];
bool vis[MAXN]; //访问标记
vector<int> G[MAXN]; //邻接表存图
int pos;
int n,root,a,b;
scanf("%d%d",&n,&root);
for(int i=1;i<n;i++)
{
scanf("%d%d",&a,&b);
G[a].push_back(b);
G[b].push_back(a);//双向存边
}
Orden DFS
Como su nombre lo indica, el orden DFS representa el orden de acceso de los dfs desde el nodo raíz del árbol.
También se puede usar como marca de tiempo para visitar cada nodo
Tome este árbol como ejemplo, el nodo raíz es 1
Buscar en orden de izquierda a derecha, luego su orden de búsqueda es
El orden en la figura se expresa como
Código de búsqueda de orden DFS
void dfs(int p)
{
dfs_order[++pos]=p; //访问到节点p时,++pos作为访问到的时间
vis[p]=true;//标记访问
for(int i:G[p])//再搜索未访问过的与p相邻的节点
if(!vis[i])
dfs(i);
}
Orden de Euler
El orden de Euler se ve casi igual que el orden de dfs
La ruta almacenada comienza desde el nodo raíz, pasa por todos los puntos en el orden de dfs y luego regresa al origen
Hay dos tipos de orden de Euler
Orden de Euler 1
Este tipo de orden de Euler es equivalente a registrar el número de nodo en la parte superior de la pila si la pila del nodo de almacenamiento cambia una vez durante dfs
En otras palabras, cada vez que visita el subárbol de un nodo, debe volver al nodo una vez y luego continuar buscando los subárboles restantes del nodo
El proceso de movimiento en el árbol es
Su orden de búsqueda es
Código de búsqueda para la orden de Euler 1
void euler_dfs1(int p)
{
euler_order1[++pos]=p;
vis[p]=true;
for(int i:G[p])
if(!vis[i])
{
euler_dfs1(i);
euler_order1[++pos]=p; //与dfs序的差别,在搜索完一棵子树后就折返一次自己
}
} //数组需要开2倍n大
Euler orden 2
Este tipo de orden de Euler es equivalente a cuando un nodo se coloca en la pila en dfs, el nodo se registra hasta que el nodo se elimina de la pila en operaciones posteriores, y el nodo se vuelve a grabar.
En otras palabras, cada nodo aparecerá estrictamente en el registro dos veces, la primera vez es cuando se busca y la segunda es cuando su subárbol se busca por completo
Excepto por el nodo raíz, cada nodo es estrictamente dos grados y dos grados
El proceso de movimiento en el árbol es
Su orden de búsqueda es
Se puede encontrar que el intervalo encerrado por dos ocurrencias de un determinado nodo en la secuencia indica que este nodo y su subárbol
Código de búsqueda para la orden 2 de Euler
void euler_dfs2(int p)
{
euler_order2[++pos]=p;
vis[p]=true;
for(int i:G[p])
if(!vis[i])
euler_dfs2(i);
euler_order2[++pos]=p; //与dfs序的差别,在所有子树搜索完后再折返自己
} //数组需要开2倍n大
Entrada de muestra y programa
Entrada de muestra:
9 1
1 2
1 3
2 4
2 5
3 6
4 7
4 8
4 9
Salida de muestra:
DFS Order :
1 2 4 7 8 9 5 3 6
Euler Order 1 :
1 2 4 7 4 8 4 9 4 2 5 2 1 3 6 3 1
Euler Order 2 :
1 2 4 7 7 8 8 9 9 4 5 5 2 3 6 6 3 1
Programa de plantilla:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e4+50;
int dfs_order[MAXN],euler_order1[MAXN*2],euler_order2[MAXN*2];
bool vis[MAXN];
vector<int> G[MAXN];
int pos;
void dfs(int p)
{
dfs_order[++pos]=p;
vis[p]=true;
for(int i:G[p])
if(!vis[i])
dfs(i);
}
void euler_dfs1(int p)
{
euler_order1[++pos]=p;
vis[p]=true;
for(int i:G[p])
if(!vis[i])
{
euler_dfs1(i);
euler_order1[++pos]=p;
}
}
void euler_dfs2(int p)
{
euler_order2[++pos]=p;
vis[p]=true;
for(int i:G[p])
if(!vis[i])
euler_dfs2(i);
euler_order2[++pos]=p;
}
int main()
{
int n,root,a,b;
scanf("%d%d",&n,&root);
for(int i=1;i<n;i++)
{
scanf("%d%d",&a,&b);
G[a].push_back(b);
G[b].push_back(a);
}
puts("DFS Order :");
memset(vis,false,n+5);
pos=0;
dfs(root);
for(int i=1;i<=pos;i++)
printf("%d ",dfs_order[i]);
putchar('\n');
puts("Euler Order 1 :");
memset(vis,false,n+5);
pos=0;
euler_dfs1(root);
for(int i=1;i<=pos;i++)
printf("%d ",euler_order1[i]);
putchar('\n');
puts("Euler Order 2 :");
memset(vis,false,n+5);
pos=0;
euler_dfs2(root);
for(int i=1;i<=pos;i++)
printf("%d ",euler_order2[i]);
putchar('\n');
return 0;
}
Ejemplos de aplicación
1 - Codeforces 1006E
preguntas desnudas en orden dfs
Un árbol de n nodos, cada borde es un borde unidireccional, y el número de n-1 en la segunda fila corresponde al número de nodo principal 2,3,4 ... nodo
q consultas, cada consulta contiene dos números uk, se requiere que la salida cuente desde k hasta el k-ésimo nodo en el orden dfs , si el nodo no existe en el subárbol de u, salida -1
Registre el tiempo de entrada, apilado de un nodo cuando se procesa la secuencia dfs, y el nodo realmente representado por la secuencia dfs.
Si dentro [u] + k-1> fuera [u], este nodo no está incluido en el subárbol de u
De lo contrario, salida dfs_order [en [u] + k-1]
#include<bits/stdc++.h>
using namespace std;
vector<int> G[200050];
int dfs_order[200050],in[200050],out[200050],pos;
void dfs(int p)
{
in[p]=++pos;
dfs_order[pos]=p;
for(int it:G[p])
dfs(it);
out[p]=pos;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int n,q,d,a,b;
cin>>n>>q;
for(int i=1;i<n;i++)
{
cin>>d;
G[d].push_back(i);
}
pos=0;
dfs(1);
while(q--)
{
cin>>a>>b;
if(in[a]+b-1>out[a])
cout<<"-1\n";
else
cout<<dfs_order[in[a]+b-1]<<'\n';
}
return 0;
}
2 - LibreOJ #144
El derecho a ser el valor de los árboles modificaciones puntuales y sub-árbol y consultas
Puede mantener una matriz tipo árbol con las marcas de tiempo en orden dfs como índice
Registre el tiempo de entrada y salida de cada nodo en la pila
Entonces este nodo y su subárbol están en el intervalo [in, out]
Para operaciones de consulta, simplemente tome sum (out) -sum (in-1) como respuesta
La operación de modificación solo necesita modificar el valor del punto en el tiempo de apilamiento en
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,ar[1000050],in[1000050],out[1000050],pos=0;
bool vis[1000050];
vector<int> G[1000050];
ll tree[1000050];
int lowbit(int x)
{
return x&(-x);
}
void add(int p,ll d)
{
while(p<=n)
{
tree[p]+=d;
p+=lowbit(p);
}
}
ll sum(int p)
{
ll r=0;
while(p>0)
{
r+=tree[p];
p-=lowbit(p);
}
return r;
}
void dfs(int p)
{
in[p]=++pos; //入栈时间
vis[p]=true;
for(int it:G[p])
if(!vis[it])
dfs(it);
out[p]=pos; //出栈时间
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int m,r,a,b,kd;
cin>>n>>m>>r;
for(int i=1;i<=n;i++)
cin>>ar[i];
for(int i=1;i<n;i++)
{
cin>>a>>b;
G[a].push_back(b);
G[b].push_back(a);
}
dfs(r);
for(int i=1;i<=n;i++)
add(in[i],ar[i]); //以时间作为索引建立树状数组
while(m--)
{
cin>>kd;
if(kd==1)
{
cin>>a>>b;
add(in[a],b);
}
else
{
cin>>a;
cout<<sum(out[a])-sum(in[a]-1)<<'\n';
}
}
return 0;
}
3 - LibreOJ #145
Del peso del árbol eran toda modificaciones sub-árboles y sub-árbol y consultas
Similar a la pregunta anterior
Puede mantener un árbol de segmentos de línea utilizando las marcas de tiempo en orden dfs como índice
Aún de acuerdo con el tiempo de pila y el tiempo de espera como índice
Luego configure un árbol de líneas con modificación y suma de intervalos.
Y debido a que un nodo del árbol de segmento de línea mantiene un intervalo, es decir, un período de tiempo
Por lo tanto, preste atención al construir un árbol y luego introduzca una matriz anti_in para registrar los datos opuestos a la matriz in
El uso específico es el siguiente
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=1e6+50;
int n,ar[MAXN];
bool vis[MAXN];
vector<int> G[MAXN];
int in[MAXN],out[MAXN],pos=0,anti_in[MAXN];
struct node
{
ll l,r,sum,lazy;
}tree[MAXN*4];
void push_up(int id)
{
tree[id].sum=tree[id<<1].sum+tree[id<<1|1].sum;
}
void push_down(int id)
{
if(tree[id].lazy)
{
int m=tree[id].r-tree[id].l+1;
tree[id<<1].lazy+=tree[id].lazy;
tree[id<<1|1].lazy+=tree[id].lazy;
tree[id<<1].sum+=tree[id].lazy*(m-(m>>1));
tree[id<<1|1].sum+=tree[id].lazy*(m>>1);
tree[id].lazy=0;
}
}
void buildTree(int id,int l,int r)
{
tree[id].l=l;
tree[id].r=r;
tree[id].lazy=0;
if(l==r)
{
tree[id].sum=ar[anti_in[l]]; //这里引用的是第l(或r)的时间访问到的节点id传给ar数组获取原有的权值
return;
}
ll mid=(l+r)>>1;
buildTree(id<<1,l,mid);
buildTree(id<<1|1,mid+1,r);
push_up(id);
}
void update(int id,int L,int R,ll val)
{
if(L<=tree[id].l&&R>=tree[id].r)
{
tree[id].sum+=val*(tree[id].r-tree[id].l+1);
tree[id].lazy+=val;
return;
}
push_down(id);
int mid=(tree[id].r+tree[id].l)>>1;
if(L<=mid)
update(id<<1,L,R,val);
if(R>mid)
update(id<<1|1,L,R,val);
push_up(id);
}
ll query_sum(int id,int L,int R)
{
if(L<=tree[id].l&&R>=tree[id].r)
return tree[id].sum;
push_down(id);
int mid=(tree[id].r+tree[id].l)>>1;
ll ans=0;
if(L<=mid)
ans+=query_sum(id<<1,L,R);
if(R>mid)
ans+=query_sum(id<<1|1,L,R);
return ans;
}
void dfs(int p)
{
in[p]=++pos; //in记录节点p入栈的时间
anti_in[pos]=p; //anti_in记录在某个时间访问到的节点的id
vis[p]=true;
for(int it:G[p])
if(!vis[it])
dfs(it);
out[p]=pos;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int m,r,a,b,kd;
cin>>n>>m>>r;
for(int i=1;i<=n;i++)
cin>>ar[i];
for(int i=1;i<n;i++)
{
cin>>a>>b;
G[a].push_back(b);
G[b].push_back(a);
}
dfs(r);
buildTree(1,1,n); //建立线段树
while(m--)
{
cin>>kd;
if(kd==1)
{
cin>>a>>b;
update(1,in[a],out[a],b);
}
else
{
cin>>a;
cout<<query_sum(1,in[a],out[a])<<'\n';
}
}
return 0;
}