7K4B blog

猫でも分かる何か

深さ優先探索 auto dfs

深さ優先探索のテンプレート

引数の頂点変数について、大雑把な方針としては

  • 頂点インデックスが不要なとき→頂点の値 vx
  • 陽にグラフを持つときなど→頂点インデックス vi

状態はオーバーヘッドが大きいことが多いのでグローバルで持ち、保存処理と復元処理を追加する
陽にグラフ構造を持つ深さ優先探索のときは頂点を記録するvis配列を用意して無限ループを防ぐ

auto dfs=[&](auto self,現在の頂点viまたはvx)->void {
	if(再帰の終了条件) return;
	(必要ならば)vis[vi]=true;
	遷移する前の計算
	for(次の頂点nviまたはnvx) {
		if(遷移しない条件) continue;
		(必要ならば)状態の保存処理
		nviまたはnvxに遷移するときの計算
		self(self,次の頂点nviまたはnvx);
		(必要ならば)状態の復元処理
	}
	全ての子頂点 (nviまたはnvx) から戻ってきたときにする計算
};


メモ化する深さ優先探索のテンプレート

ABC340C - Divide and Divide
ソースコード

  • いわゆるメモ化と言われているテク、mapを参照することで計算量を削減する
  • sum_cvx[vx] (sum child vx) の計算は全ての子頂点から帰ってきた後なので for の後に書く
  • dfs実行前にsum_cvx[1]の値を初期化しないとout of rangeになるので注意する
mll sum_cvx;
auto dfs lam(auto self,ll vx)vo {
	if(vx==1) return;
	
	vl nvxs;
	if(!lfind(sum_cvx,ldivc(vx,2))) pushb(nvxs,ldivc(vx,2));
	if(!lfind(sum_cvx,ldivf(vx,2))) pushb(nvxs,ldivf(vx,2));
	forx(nvx,nvxs) self(self,nvx);
	sum_cvx[vx]=vx+sum_cvx[ldivc(vx,2)]+sum_cvx[ldivf(vx,2)];
};
sum_cvx[1]=0;
dfs(dfs,N);
  • ただし、単純な末尾再帰の場合はコード量の少ないアドホックな書き方もある
mll sum_cvx;
auto dfs lam(auto self,ll vx)ll {
	if(lfind(sum_cvx,vx)) return sum_cvx[vx];
	sum_cvx[vx]=vx+self(self,ldivc(vx,2))+self(self,ldivf(vx,2));
	return sum_cvx[vx];
};
sum_cvx[1]=0;
dfs(dfs,N);
  • OEIS を使えば1行で済む
out(N*llogf(2,4*N)-lpow(2,(llogf(2,2*N))));

陽にグラフを持つ深さ優先探索のテンプレート

ABC138D - Ki
ソースコード

  • 頂点に遷移したかどうか記録するvis配列を用意して無限ループを防ぐ
  • sum_pvx[nvi] (sum parent vx) は親頂点の合計になるので、遷移直前に計算する
  • dfs実行前に根の初期化を忘れないようにする
vb vis(N);
vl sum_pvx(N);
auto dfs lam(auto self,ll vi)vo {

	vis[vi]=true;
	forx(nvi,nvx,g[vi]) {
		if(!!vis[nvi]) continue;
		sum_pvx[nvi]=sum_px[nvi]+sum_pvx[vi];
		self(self,nvi);
	}
};
sum_pvx[0]=sum_px[0];
dfs(dfs,0);