본문 바로가기
알고리즘/종만북

WORDCHAIN - 단어제한 끝말잇기 (오일러 경로, 헤밀토니안 경로)

by sun__ 2020. 2. 12.

https://algospot.com/judge/problem/read/WORDCHAIN

헤밀턴 경로 문제를 오일러경로 문제로 바꾸는 것이 포인트.

모든 정점을 지나는 경로->모든 간선을 지나는 경로

https://suuntree.tistory.com/176코드자세한설명

 

 

<문제설명>

단어들이 최대 100개 주어질 때, 이 단어들을 단 한번씩만 이용해서 모든 단어들을 사용할 수 있는지 출력. 사용할 수 있다면 그 순서중 아무거나 출력.

 

<풀이>

모든 단어들을 사용할수 있는지에 대해 그래프를 형성하면, 헤밀토니안 경로를 찾는 문제가 된다.(모든 정점을 정확히 한 번씩 지나는 경로. 외판원 순회(TSP)는 이 경로 중 가장 작은 가중치를 갖는 경로.)

 

헤밀턴 경로를 찾는 유일한 방법은 조합탐색으로 이 문제의 경우 단어 개수 n!만큼의 시간이 걸린다. ->불가능

 

알파벳을 정점으로, 단어를 간선으로 생각한다면 오일러 경로를 찾는 문제가 된다.

 

getEuler에서 파라미터로 벡터를 넘겨 갱신하는 이유는, 함수 자체가 벡터를 반환하는 형태

 

<코드>

int n, adj[26][26], ind[26], oud[26]; //단방향그래프이므로 adj외에 진입진출차수를 유지해야 한다.
vector<string> edge[26][26]; //i에서시작해서 j로 끝나는 단어를 정점 i~j를 잇는 간선으로 본다.

void init() {
	ans.clear();
	for (int i = 0; i < 26; i++) {
		for (int j = 0; j < 26; j++) {
			edge[i][j].clear();
			adj[i][j] = 0;
		}
		ind[i] = oud[i] = 0;
	}	
}

int trailOrCircuit() {
	int s = 0, e = 0;
	for (int i = 0; i < 26; i++) {
		int t = oud[i] - ind[i];		
		if (t == 1) s++;
		if (t == -1)e++;
	}

	if (s == 1 && e == 1) return 1;
	if (s == 0 && e == 0) return 2;
	return 0;
}

vector<int> ans;
void getEuler(int u) {
	for(int v=0; v<26; v++)
		while (adj[u][v] > 0) {
			adj[u][v]--;
			getEuler(v);
		}
	ans.push_back(u);
}

int main() {
	FAST;
	int tt; cin >> tt;
	while (tt--) {
		init(); cin >> n;
		vector<string> words(n);
		for (int i = 0; i < n; i++) cin >> words[i];
		for (int i = 0; i < words.size(); i++) {
			int a = words[i][0] - 'a';
			int b = words[i].back() - 'a';
			edge[a][b].push_back(words[i]);
			adj[a][b]++;
			oud[a]++;
			ind[b]++;
		}

		int t = trailOrCircuit();
		if (t == 0) {
			cout << "IMPOSSIBLE\n";
			continue;
		}
		else if (t == 1) { //트레일 가능성
			for (int i = 0; i < 26; i++) 
				if (oud[i] == ind[i] + 1) {
					getEuler(i);
					break; //가능성 있는 시작점 아무데나 하나만 보면 된다.
				}
		}
		else { //서킷 가능
			for(int i=0; i<26; i++)
				if (oud[i]) { 
					getEuler(i);
					break; //가능성 있는 시작점 아무데나 하나만 보면 된다.
				}
		}

		if (ans.size() != n + 1) {
			cout << "IMPOSSIBLE\n";
			continue;
		}

		reverse(ans.begin(), ans.end()); //방향그래프이므로 뒤집어줘야 한다.
		for (int i = 1; i < ans.size(); i++) {
			int a = ans[i - 1], b = ans[i];
			cout << edge[a][b].back() << " ";
			edge[a][b].pop_back();
		}
		cout << '\n';
	}
}

 

<생각>

모든 정점을 지나는 경로에 대한 문제가 모든 간선을 지나는 경로에 대한 문제보다 훨씬 어려운 문제라는 것에 유념.

이 문제처럼 모든 간선에 대한 문제로 바꿔준다면 시간안에 풀 수 있다.

'알고리즘 > 종만북' 카테고리의 다른 글

이중 연결 요소(BCC), 단절점, 단절선  (0) 2020.02.13
DFS트리에서 간선의 종류, 사이클 검출  (0) 2020.02.13
오일러 경로  (0) 2020.02.12
DICTIONARY - 고대어 사전 (위상정렬)  (0) 2020.02.12
펜윅 트리  (0) 2020.02.10