VK Cup 2015 - Finals A. Matching Names
問題概要
n 個の名前と、n 個のペンネームが与えられる。これらを1対1対応させることを考える。
名前 a とペンネーム b をペアにした時の満足度を lcp(a, b) と定義する。
lcp(a, b) の総和が最大となるような、1対1対応を出力せよ。
1 <= n <= 10^5
文字列の長さの総和は 8 * 10^5
解法
lcp なので脳死で SA から攻めたくなりそうだが、lcp を Trie の上で考える視点も必要。
Trie における2文字列間の lcp は、LCA になる。
また、lcp(a, b) = (|a| + |b| - (|a| + |b| - 2 * lcp(a, b)) / 2 であることを考えると、lcp(a, b) の総和の最大化は(|a|, |b| が定数なので)以下のように言い換えられる。
木の上に n 頂点ずつ位置が与えられるので、それらをいい感じ組分けして最短距離の和の最小化をせよ
これは Trie を構築した後に dfs で下の方から貪欲的にペアを作っていけば達成できる。形式的に証明しなくても直感的にわかると思う。
これで計算量は O(total_length) となる。
ソースコード
#include <bits/stdc++.h> using namespace std; using pii = pair<int, int>; struct alphabet_t { // default: 'a' - 'z' static constexpr int size = 26; static constexpr int char_to_index(char c) { assert('a' <= c && c <= 'z'); return c - 'a'; } static constexpr char index_to_char(int idx) { assert(0 <= idx && idx < size); return 'a' + idx; } }; template <typename Alpha> class trie { static constexpr int alpha_sz = Alpha::size; public: void insert(std::string const& s, int tag, int idx) { auto cur_node = this; for(int p = 0; p < static_cast<int>(s.size()); ++p) { const auto c = Alpha::char_to_index(s[p]); if(!cur_node->next[c]) cur_node->next[c] = std::make_unique<trie<Alpha>>(); cur_node = cur_node->next[c].get(); } cur_node->idxs[tag].push_back(idx); } // 本質 pair<int, array<vector<int>, 2>> solve(std::vector<pii>& ans) { auto res = make_pair(0, idxs); for(int i = 0; i < alpha_sz; ++i) { if(!next[i]) continue; auto ch = next[i]->solve(ans); res.second[0].insert(end(res.second[0]), begin(ch.second[0]), end(ch.second[0])); res.second[1].insert(end(res.second[1]), begin(ch.second[1]), end(ch.second[1])); res.first += ch.first; } while(!res.second[0].empty() && !res.second[1].empty()) { ans.emplace_back(res.second[0].back(), res.second[1].back()); res.second[0].pop_back(); res.second[1].pop_back(); } assert(res.second[0].empty() || res.second[1].empty()); res.first += res.second[0].size() + res.second[1].size(); return res; } private: std::array<std::unique_ptr<trie<Alpha>>, alpha_sz> next; array<vector<int>, 2> idxs; }; int main() { int n; cin >> n; trie<alphabet_t> t; int total_len = 0; for(int i = 0; i < 2 * n; ++i) { string s; cin >> s; t.insert(s, i >= n, (i >= n ? i - n + 1 : i + 1)); total_len += s.size(); } vector<pii> ans; cout << (total_len - t.solve(ans).first) / 2 << endl; for(auto& p : ans) { cout << p.first << " " << p.second << endl; } }