[アルゴリズム]ツリーDFS順序とオイラー順序-DFSタイムスタンプとループアクセス順序



この記事で使用されているフロント入力と画像の保存方法を記述します。

入力方法:

  最初の行の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のアクセス順序を表します

各ノードを訪問するためのタイムスタンプとしても使用できます

PIC1

このツリーを例にとると、ルートノードは1です。

左から右の順に検索すると、その検索順序は

PIC2

図中の順番は

PIC3



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回変更された場合、スタックの一番上にノード番号を記録することと同等です。

つまり、ノードのサブツリーにアクセスするときはいつでも、ノードに一度戻ってから、ノードの残りのサブツリーを引き続き検索する必要があります。

ツリー上の移動プロセスは

PIC4

その検索順序は

PIC5



オイラー次数の検索コード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度です

ツリー上の移動プロセスは

PIC6

その検索順序は

PIC7

シーケンス内の特定のノードの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

1006E。軍事問題

確率-1-1
確率-1-2
確率-1-3

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

#144. DFSシーケンス1

確率-2

ツリーの値に右点修正サブツリーとクエリ

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

#145. DFSシーケンス2

確率-3

ツリーの重さであった全サブツリー修飾サブツリーとクエリ

前の質問と同様

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;
}

おすすめ

転載: www.cnblogs.com/stelayuri/p/12702684.html