公式

D - Paid Walk 解説 by mechanicalpenciI


頂点 \(1\) からスタートしてちょうど \(L\) 回辺を通るような経路は、深さ優先探索 (DFS) などによって列挙することが可能です。
頂点 \(v\) \((1\leq v\leq N)\) が問題の条件をみたすかどうかは、 これらの経路のうち、通った辺のコストの総和が \(S\) 以上 \(T\) 以下であるようなものであって、\(L\) 回目の移動の後にいる頂点が \(v\) であるようなものが存在することと同値です。

よって、頂点 \(1\) でまだ何の辺も通ってない状態から始めて、(現在いる頂点, それまでに通った辺の本数, それまでに通った辺のコストの総和)の情報を持って、次の手順を再帰的に行うことによって解くことができます。

  • それまでに通った辺の本数が \(L-1\) 以下ならば、現在いる頂点から伸びている辺すべてについて、「その辺を通って移動する。このとき、(現在いる頂点, それまでに通った辺の本数, それまでに通った辺のコストの総和)を通った辺の情報によって更新する。その後、再度この手順を行う。」という行動を行う。
  • それまでに通った辺の本数が \(L\) のとき、それまでに通った辺のコストの総和が \(S\) 以上 \(T\) 以下ならば、現在いる頂点を(問題文の)条件をみたす頂点として記録する。

この手順のあとで、条件をみたす頂点として記録された頂点を(重複しないように)昇順に列挙したものが答えとなります。

ここで、考える必要があるのが列挙するのにかかる時間計算量ですが、重要となるのは以下の事実です。

この問題で与えられるグラフにおいて、頂点 \(1\) からスタートしてちょうど \(L\) 回辺を通るような経路は、高々 \(4^L\) 通りしか存在しない。
なお、ここで \(2\) つの経路が異なるとは、ある \(1\leq i\leq N\) が存在して \(i\) 回目に通った辺が異なることを指す。

証明 $L$ についての数学的帰納法により示します。 $L=1$ のとき、頂点 $1$ から出ている辺に沿って移動するほかなく、頂点 $1$ の出次数が高々 $4$ であることから従います。 $L=k+1$ $(k\geq 1)$ のときについて考えます。ちょうど $k+1$ 回辺を通る経路は、ちょうど $k$ 回辺を通る経路のうちの $1$ つに、$k$ 回目の移動の後の頂点から伸びている辺のうち $1$ つを通って移動することを付け加えたものになります。ちょうど $k$ 回辺を通る経路は仮定より高々 $4^k$ 通りです。よって、各頂点の出次数が高々 $4$ であることから、ちょうど $k+1$ 回辺を通る経路は高々 $4^k\times 4=4^{k+1}$ 通りとなります。 よって、示されました。

よって、上記の方法では高々 \(O(L\cdot 4^L)\) ですべての経路を列挙することができます。なお実際には、\(i\) 本の辺を通った後の状態が高々 \(4^i\) 通りしか存在しないことから、上記の深さ優先探索の手順の計算量は \(O(4^L)\) となっています。
これは、本問題の制約(特に \(L\leq 10\) )下で、十分に高速です。よって、この問題を解くことができました。

C++ による実装例:

#include <bits/stdc++.h>
using namespace std;

#define N (int)200000

int l,s,t;
vector<pair<int,int> >e[N];
bool flag[N];


void dfs(int k,int d,int tot){
	if(d==l){
	  if((s<=tot)&&(tot<=t))flag[k]=true;
	  return;
	}
	if(d<l){ //just in case
		int sz=e[k].size();
		for(int i=0;i<sz;i++){
			dfs(e[k][i].first,d+1,tot+e[k][i].second);
		}
	}
	return;
}


int main() {
	int n,m;
	int u,v,c;
	cin >> n >> m >> l >> s >> t;
	for(int i=0;i<m;i++){
		cin >> u >> v >> c;
		e[u-1].push_back({v-1,c});
	}
	for(int i=0;i<n;i++)flag[i]=false;
	dfs(0,0,0);

	vector<int>ans;
	for(int i=0;i<n;i++){if(flag[i])ans.push_back(i+1);}
	int sz=ans.size();
	for(int i=0;i<sz;i++){
		cout << ans[i];
		if(i<(sz-1))cout << " ";
	}
	cout << endl;
	return 0;
}

投稿日時:
最終更新: