2867. Count Valid Paths in a Tree

풀이

  • μ œν•œ μ‹œκ°„ 내에 ν’€μ§€ λͺ»ν–ˆμœΌλ©° 해닡을 μ°Έκ³ ν•˜μ˜€λ‹€.
  • μ—λΌν† μŠ€ν…Œλ„€μŠ€μ˜ μ²΄λŠ” μ΄λ²ˆμ—μ•Ό λΉ„λ‘œμ†Œ 이해λ₯Ό μ’€ ν•œ λ“― ν•˜λ‹€.
  • DFS둜 ν’€μ–΄μ•Ό ν•˜λŠ” 것은 λ§žμ§€λ§Œ DPλ₯Ό μ μš©ν•˜λŠ” λΆ€λΆ„μ—μ„œ μ „ν˜€ 감이 μž‘νžˆμ§€ μ•Šμ•˜μ—ˆλ‹€.
  • DP 뢀뢄은 ν˜„μž¬ λ…Έλ“œμ˜ μƒνƒœ 값을 ν•˜μœ„ λ…Έλ“œλ“€μ˜ μƒνƒœκ°’μ„ μ΄μš©ν•΄μ„œ λ§Œλ“€μ–΄μ•Ό ν•œλ‹€.
    • μƒνƒœ 값은 prime nodeκ°€ ν¬ν•¨λ˜μ§€ μ•Šμ€ 경우 curr[0]와 ν¬ν•¨λœ 경우 curr[1] λ₯Ό λ”°λ‘œ 좔적해야 ν•œλ‹€.
    • ν˜„μž¬ λ…Έλ“œκ°€ prime이라면
      • prime nodeκ°€ ν¬ν•¨λœ 경우의 수 curr[1]에 ν•˜μœ„ λ…Έλ“œ 쀑 prime nodeκ°€ 포함 μ•ˆλœ 경우의 수 next[0] 만 μΆ”κ°€ν•΄μ•Ό ν•œλ‹€.
      • ν•˜μœ„ λ…Έλ“œ 쀑 prime nodeκ°€ ν¬ν•¨λœ 경우의 μˆ˜λŠ” ν¬ν•¨μ‹œν‚¬ 수 μ—†λ‹€. μ™œλƒν•˜λ©΄ κ·Έλ ‡κ²Œ 되면 prime nodeκ°€ 2개 이상 ν¬ν•¨λœ κ²½λ‘œκ°€ 되기 λ•Œλ¬Έμ΄λ‹€.
    • ν˜„μž¬ λ…Έλ“œκ°€ prime이 μ•„λ‹ˆλΌλ©΄
      • prime nodeκ°€ ν¬ν•¨λ˜μ§€ μ•Šμ€ 경우의 수 curr[0]에 ν•˜μœ„ λ…Έλ“œ 쀑 prime nodeκ°€ 포함 μ•ˆλœ 경우의 수 next[0] λ₯Ό μΆ”κ°€ν•œλ‹€.
      • prime nodeκ°€ ν¬ν•¨λœ 경우의 수 curr[1] μ—λŠ” ν•˜μœ„ λ…Έλ“œ 쀑 prime nodeλ₯Ό ν¬ν•¨ν•œ 경우의 수 next[1]λ₯Ό μΆ”κ°€ν•œλ‹€.
  • 제일 이해가 μ–΄λ €μ› λ˜ 뢀뢄은 경둜의 수λ₯Ό κ³„μ‚°ν•˜λŠ” λΆ€λΆ„μ΄μ—ˆλŠ”λ° μ•„λž˜μ™€ 같이 계산할 수 μžˆλ‹€.
    • ν˜„μž¬ λ…Έλ“œκ°€ prime node라면
      • ν•˜μœ„ λ…Έλ“œ 쀑 prime nodeκ°€ μ•„λ‹Œ 경우의 수 next[0] λ₯Ό λͺ¨λ‘ 결과값에 λ”ν•œλ‹€.
      • 그리고 ν•˜μœ„ λ…Έλ“œ 쀑 2개λ₯Ό 선택 (combination) ν•˜μ—¬ 각각 prime nodeκ°€ μ•„λ‹Œ 경우의 수 next[0] λ₯Ό κ΅¬ν•˜μ—¬ κ³±ν•œ λ’€ 결과값에 λ”ν•œλ‹€.
        • prime nodeλ₯Ό ν¬ν•¨ν•˜μ§€ μ•ŠλŠ” 두 경둜 + ν˜„μž¬ λ…Έλ“œ (prime)λ₯Ό ν•©ν•œ 것도 μƒˆλ‘œμš΄ κ²½λ‘œκ°€ λœλ‹€.
    • ν˜„μž¬ λ…Έλ“œκ°€ prime nodeκ°€ μ•„λ‹ˆλΌλ©΄
      • ν•˜μœ„ λ…Έλ“œ 쀑 prime node인 경우의 수 next[1] λ₯Ό λͺ¨λ‘ 결과값에 λ”ν•œλ‹€.
      • 그리고 ν•˜μœ„ λ…Έλ“œ 쀑 2개λ₯Ό 선택 (combination)ν•˜μ—¬ ν•œμͺ½μ€ prime nodeλ₯Ό ν¬ν•¨ν•œ 경우 next[0], λ‹€λ₯Έμͺ½μ€ prime nodeλ₯Ό ν¬ν•¨ν•˜μ§€ μ•Šμ€ 경우 next[1]λ₯Ό μ„ νƒν•˜μ—¬ κ³±ν•œ λ’€ 결과값에 λ”ν•œλ‹€.
        • prime nodeλ₯Ό ν¬ν•¨ν•˜λŠ” 경둜 + ν˜„μž¬ λ…Έλ“œ (not prime) + prime nodeλ₯Ό ν¬ν•¨ν•˜μ§€ μ•ŠλŠ” 경둜λ₯Ό ν•©ν•œ 것도 μƒˆλ‘œμš΄ κ²½λ‘œκ°€ λœλ‹€.
    • 근데 μœ„μ˜ λ‚΄μš©μ„ κ·ΈλŒ€λ‘œ κ΅¬ν˜„ν•˜κ²Œ 되면 timeout이 λ°œμƒν•˜κ²Œ λœλ‹€.
      • μœ„μ˜ λ‚΄μš©μ„ μžˆλŠ” κ·ΈλŒ€λ‘œ κ΅¬ν˜„ν•œκ²ƒμ€ ν•˜λ‹¨μ— μΆ”κ°€λ‘œ λΆ™μ—¬λ‘μ—ˆλ‹€.
Β 
class Solution { public: vector<bool> get_prime_table(int n) { vector<bool> result(n + 1); fill(result.begin(), result.end(), true); result[0] = result[1] = false; int p = 2; while (p <= n) { if (!result[p]) ++p; int c = p + p; while (c <= n) { result[c] = false; c += p; } ++p; } return result; } array<long long, 2> dfs( unordered_map<int, vector<int>>& adj, vector<bool>& is_prime, int node, int parent, long long& result) { array<long long, 2> curr = {}; auto prime = is_prime[node]? 1 : 0; ++curr[prime]; for (auto child : adj[node]) { if (child == parent) continue; auto next = dfs(adj, is_prime, child, node, result); // non prime cases of others x prime cases of next result += curr[0] * next[1]; // prime cases of others x non prime cases of next result += curr[1] * next[0]; // add non prime cases curr[prime] += next[0]; // count prime cases if current is not prime if (!prime) curr[1] += next[1]; } return curr; } long long countPaths(int n, vector<vector<int>>& edges) { auto is_prime = get_prime_table(n); unordered_map<int, vector<int>> adj; for (auto& edge : edges) { adj[edge[0]].push_back(edge[1]); adj[edge[1]].push_back(edge[0]); } long long result = 0; dfs(adj, is_prime, 1, -1, result); return result; } };

DFSλ₯Ό μ„€λͺ… κ·ΈλŒ€λ‘œ κ΅¬ν˜„ν•˜λ©΄?

  • dfs 뢀뢄을 μ„€λͺ… κ·ΈλŒ€λ‘œ κ΅¬ν˜„ν•œ 것은 μ•„λž˜μ™€ κ°™λ‹€.
    • ν•˜μœ„ λ…Έλ“œ 2개의 쑰합에 따라 κ³„μ‚°ν•˜λŠ” 뢀뢄이 μΆ”κ°€λ˜μ—ˆκ³  이둜 μΈν•΄μ„œ μ‹œκ°„ μ œν•œμ— 걸리게 λœλ‹€.
    • μ΅œμ•…μ˜ 경우 ν•˜μœ„ λ…Έλ“œκ°€ n에 κ·Όμ ‘ν•œ μˆ«μžκ°€ 되면 만큼의 연산을 μˆ˜ν–‰ν•΄μ•Ό ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.
array<long long, 2> dfs( unordered_map<int, vector<int>>& adj, vector<bool>& is_prime, int node, int parent, long long& result) { array<long long, 2> curr = {}; auto prime = is_prime[node]? 1 : 0; ++curr[prime]; // tracking current states vector<array<long long, 2>> nexts; for (auto child : adj[node]) { if (child == parent) continue; auto next = dfs(adj, is_prime, child, node, result); nexts.push_back(next); // add non prime cases curr[prime] += next[0]; // count prime cases if current is not prime if (!prime) curr[1] += next[1]; } // calculate result (n^2 time complexity!!!) int M = nexts.size(); if (prime) { for (auto& next : nexts) { result += next[0]; } for (int i = 0; i < M - 1; ++i) { for (int j = i + 1; j < M; ++j) { result += nexts[i][0] * nexts[j][0]; } } } else { for (auto& next : nexts) { result += next[1]; } for (int i = 0; i < M - 1; ++i) { for (int j = i + 1; j < M; ++j) { result += nexts[i][0] * nexts[j][1]; result += nexts[i][1] * nexts[j][0]; } } } return curr; }

μ΅œμ ν™” ν•˜κΈ°

  • μ‹œκ°„ 초과λ₯Ό λ°œμƒμ‹œν‚€λŠ” μ΄μœ λŠ” 두 ν•˜μœ„ λ…Έλ“œμ˜ 값을 κ³±ν•΄μ„œ λ”ν•˜λŠ” κ²ƒμ˜ μ‹œκ°„ λ³΅μž‘λ„κ°€ 이기 λ•Œλ¬Έμ΄λ‹€. λ”°λΌμ„œ 이 뢀뢄을 μ΅œμ ν™”ν•œλ‹€.
  • μ–΄λ–€ μ§‘ν•© κ°€ μžˆμ„ λ•Œ μ§‘ν•©μ—μ„œ 2개의 숫자λ₯Ό κ³¨λΌμ„œ κ³±ν•œ κ°’μ˜ 합은 λ‹€μŒκ³Ό 같이 ꡬ할 수 μžˆλ‹€.
  • λ”°λΌμ„œ 각 ν•­λͺ©μ˜ 합을 μΆ”μ ν•˜λŠ” λ³€μˆ˜ sum 와 ν˜„μž¬ κ°’ a[i] 을 κ³±ν•˜μ—¬ 결과에 λ”ν•˜λ©΄ result += sum * a[i] 2개의 숫자λ₯Ό κ³¨λΌμ„œ κ³±ν•œ κ°’μ˜ 합이 λœλ‹€.
array<long long, 2> dfs( unordered_map<int, vector<int>>& adj, vector<bool>& is_prime, int node, int parent, long long& result) { array<long long, 2> curr = {}; auto prime = is_prime[node]? 1 : 0; ++curr[prime]; // tracking current states vector<array<long long, 2>> nexts; for (auto child : adj[node]) { if (child == parent) continue; auto next = dfs(adj, is_prime, child, node, result); nexts.push_back(next); // add non prime nodes curr[prime] += next[0]; // count prime nodes if current is not prime if (!prime) curr[1] += next[1]; } // calculate result if (prime) { // add non prime cases for (auto& next : nexts) { result += next[0]; } // add multiplication of two non prime cases long long nonPrimeSum = 0; for (auto& next : nexts) { result += nonPrimeSum * next[0]; nonPrimeSum += next[0]; } } else { // add prime cases for (auto& next : nexts) { result += next[1]; } // add multiplication of prime cases & non prime cases long long nonPrimeSum = 0; long long primeSum = 0; for (auto& next : nexts) { result += nonPrimeSum * next[1]; result += primeSum * next[0]; nonPrimeSum += next[0]; primeSum += next[1]; } } return curr; }
  • μ’€ 더 μ΅œμ ν™” ν•˜μžλ©΄ sum λ‘œμ§μ„ μ•„λž˜μ²˜λŸΌ ν•©μΉ  수 μžˆλ‹€.
// calculate result int notPrime = prime == 1? 0 : 1; for (auto& next : nexts) { result += next[notPrime]; } array<long long, 2> sum = {0, 0}; for (auto& next : nexts) { result += sum[0] * next[1]; result += sum[1] * next[0]; sum[prime] += next[0]; if (!prime) sum[1] += next[1]; } return curr;
  • 더 μ΅œμ ν™” ν•˜λ©΄ μ•„λž˜μ™€ κ°™λ‹€.
// calculate result array<long long, 2> sum = {0, 0}; ++sum[prime]; for (auto& next : nexts) { result += sum[0] * next[1]; result += sum[1] * next[0]; sum[prime] += next[0]; if (!prime) sum[1] += next[1]; } return curr;
  • μ—¬κΈ°μ„œ μ’€ 더 μ΅œμ ν™”ν•˜λ©΄ nextsλ₯Ό μ œκ±°ν•  수 μžˆλ‹€. μ œκ±°ν•œ κ²°κ³ΌλŠ” λ³Έ νŽ˜μ΄μ§€ μ²˜μŒμ— μžˆλŠ” μ†”λ£¨μ…˜μ„ μ°Έκ³ ν•˜λ©΄ λœλ‹€.