알고리즘/알고리즘 문제 해결전략(종만북)
28장 단어 제한 끝말잇기 WORDCHAIN
pureworld
2019. 1. 5. 19:06
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | #include <iostream> #include <queue> #include<deque> #include <stack> #include<string> #include<algorithm> #include<vector> using namespace std; //그래프의 인접 행렬 표현,adj[i][j]=i와 j사이의 간선의 수 vector<vector<int> > adj; //graph[i][j]=i로 시작해서 j로 끝나는 단어의 목록 vector<string> graph[26][26]; //indegree[i]=i로 시작하는 단어의 수 //outdegree[i]=i로 끝나는 단어의 수 vector<int> indegree, outdegree; //유향 그래프의 인접행렬 adj가 주어질 때 오일러 서킷 혹은 트레일을 계산한다. void getEulerCircuit(int here, vector<int>& circuit) { for (int there = 0; there < adj[here].size(); there++) { while (adj[here][there] > 0) { adj[here][there]--;//간선을 지운다 getEulerCircuit(there, circuit); } } circuit.push_back(here); } //현재 그래프의 오일러 트레일이나 서킷을 반환한다. vector<int> getEulerTrailOrCircuit() { vector<int> circuit; //우선 트레일을 찾아본다:시작점이 존재하는 경우 for(int i=0;i<26;i++) if (outdegree[i] == indegree[i] + 1) { getEulerCircuit(i, circuit); return circuit; } //아니면 서킷이니,간선에 인접한 아무 정점에서나 시작한다. for(int i=0;i<26;i++) if (outdegree[i]) { getEulerCircuit(i, circuit); return circuit; } //모두 실패한 경우 빈 배열을 반환한다. return circuit; } //현재 그래프의 오일러 서킷/트레일 여부를 확인한다. bool checkEuler() { //예비 시작점과 끝점의 수 int plus1 = 0, minus1 = 0; for (int i = 0; i < 26; i++) { int delta = outdegree[i] - indegree[i]; //모든 정점의 차수는 -1,1 또는 0이어야 한다. if (delta < -1 || 1 < delta) return false; if (delta == 1)plus1++; if (delta == -1)minus1++; } //시작점과 끝점은 각 하나씩 있거나 하나도 없어야 한다. return(plus1 == 1 && minus1 == 1) || (plus1 == 0 && minus1 == 0); } void makegraph(const vector<string>& words) { //전역 변수 초기화 for (int i = 0; i < 26; i++) for (int j = 0; j < 26; j++) graph[i][j].clear(); adj = vector<vector<int> >(26, vector<int>(26, 0)); indegree = outdegree = vector<int>(26, 0); //각 단어를 그래프에 추가한다. for (int i = 0; i < words.size(); i++) { int a = words[i][0] - 'a'; int b = words[i][words[i].size() - 1] - 'a'; graph[a][b].push_back(words[i]); adj[a][b]++; outdegree[a]++; indegree[b]++; } } string solve(const vector<string>& words) { makegraph(words); //차수가 맞지않으면 실패! if (!checkEuler()) return "IMPOSSIBLE"; //오일러 서킷이나 경로를 찾아낸다. vector<int> circuit = getEulerTrailOrCircuit(); //모든 간선을 방문하지 못했으면 실패! if (circuit.size() != words.size() + 1) return "IMPOSSIBLE"; //아닌 경우 방문순서를 뒤집은 뒤 간선들을 모아 문자열로 만들어 반환한다. reverse(circuit.begin(), circuit.end()); string ret; for (int i = 1; i < circuit.size(); i++) { int a = circuit[i - 1], b = circuit[i]; if (ret.size()) ret += " "; ret += graph[a][b].back(); graph[a][b].pop_back(); } return ret; } int main(void) { int cases; cin >> cases; while (cases--) { adj.clear(); int n; cin >> n; vector<string> words; for (int i = 0; i < n; i++) { string s; cin >> s; words.push_back(s); } cout << solve(words) << endl; } return 0; | cs |
문제 출처:https://algospot.com/judge/problem/read/WORDCHAIN
문제를 푸는 핵심은
1. 주어진 각 단어를 정점이 아니라 간선으로 갖는 방향 그래프를 만드는 것
why? 단어를 정점으로 만들면 최악의 경우 n!개의 후보를 만들어 봐야 하기 때문
2. 오일러 트레일 혹은 서킷을 구분해서 푸는 것
방향 그래프에서
오일러 서킷: 각 정점에 들어오는 간선의 수와 나가는 간선의 수가 같아야 함.
오일러 트레일: a에서는 나가는 간선이 들어오는 간선보다 하나 많고, b는 들어오는 간선이
나가는 간선 보다 하나 많고,다른 모든 정점에서는 나가는 간선과 들어오는 간선의 수가 같아야 합니다.
이 코드의 시간 복잡도는 오일러 트레일을 찾는 함수에 의해 지배됩니다.
그래프를 만드는 과정과 답으로 출력할 문자열을 만드는 과정은 모두 단어의
수에 비례하는 시간이 걸리는데, 오일러 트레일을 찾는 함수의 수행 시간은 알파벳의 수 A와
단어의 수 n의 곱에 비례하기 때문입니다.
따라서 전체 시간 복잡도는 O(nA)가 됩니다.