この記事で使用されているフロント入力と画像の保存方法を記述します。
入力方法:
最初の行の2つの番号nとルートは、ツリーに合計n個のノードがあることを示します。そのうち、番号ルートはルートノードです。
次のn-1行、1行あたり2つの整数ab、ノードaがノード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);//双向存边
}
DFS注文
名前が示すように、DFS順序は、ツリーのルートノードからのDFSのアクセス順序を表します
各ノードを訪問するためのタイムスタンプとしても使用できます
このツリーを例にとると、ルートノードは1です。
左から右の順に検索すると、その検索順序は
図中の順番は
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);
}
オイラー次数
オイラー次数はdfs次数とほぼ同じように見える
保存されたパスは、ルートノードから始まり、dfsの順序ですべてのポイントを通過してから、原点に戻ります
オイラー次数には2種類あります
オイラー次数1
この種類のオイラー順序は、ストレージノードのスタックがDFS中に1回変更された場合、スタックの一番上にノード番号を記録することと同等です。
つまり、ノードのサブツリーにアクセスするときはいつでも、ノードに一度戻ってから、ノードの残りのサブツリーを引き続き検索する必要があります。
ツリー上の移動プロセスは
その検索順序は
オイラー次数の検索コード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大
オイラー次数2
この種類のオイラー順序は、ノードがdfsのスタックに置かれた場合、ノードが後続の操作でスタックからプッシュオフされるまで記録され、ノードが再び記録されるのと同じです。
つまり、各ノードはレコードに2回厳密に表示されます。1回目はノードが検索され、2回目はそのサブツリーが完全に検索されます。
ルートノードを除き、各ノードは厳密に2度と2度です
ツリー上の移動プロセスは
その検索順序は
シーケンス内の特定のノードの2つの出現によって囲まれた間隔は、このノードとそのサブツリーが
オイラー次数2の検索コード
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大
サンプル入力とプログラム
入力例:
9 1
1 2
1 3
2 4
2 5
3 6
4 7
4 8
4 9
出力例:
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
テンプレートプログラム:
#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;
}
応用例
1-Codeforces 1006E
DFS順で裸の質問
nノードのツリー。各エッジは一方向のエッジであり、2行目のn-1の数は、2、3、4 ...ノードの親ノード番号に対応します。
qクエリ、各クエリには2つの数値ukが含まれます。ノードがuのサブツリーに存在しない場合、出力-1 は、dfs順序でkからk番目のノードまでカウントする必要があります。
時間を記録し、dfsシーケンスが処理されたときにノードからスタックし、ノードは実際にdfsシーケンスで表されます。
in [u] + k-1> out [u]の場合、このノードはuのサブツリーに含まれません
それ以外の場合は、dfs_order [in [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
ツリーの値に右点修正とサブツリーとクエリ
dfs順のタイムスタンプをインデックスとしてツリーのような配列を維持できます
スタック上の各ノードの出入りを記録する
次に、このノードとそのサブツリーは[in、out]の間隔にあります
クエリ操作の場合は、合計(out)-sum(in-1)を答えとしてください
変更操作では、スタック時間のポイントの値を変更するだけで済みます
#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
ツリーの重さであった全サブツリー修飾とサブツリーとクエリ
前の質問と同様
dfs順のタイムスタンプをインデックスとして使用して、ラインセグメントツリーを維持できます。
まだ、スタックタイムインとスタックタイムアウトによると、
次に、間隔の変更と間隔の合計を使用してラインツリーを設定します。
また、線分ツリーのノードは間隔、つまり期間を維持するため
したがって、ツリーを構築するときは注意して、in_in配列の反対のデータを記録するためにanti_in配列を導入
具体的な使い方は以下の通りです
#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;
}