题目大意:给你两堆n个字符串,让你两两配对使得总LCP长度最大.并且需要知道具体方案
题目思路:
贪心:根据LCP从大往小的去匹配.这样一定是最优的.
我想可以利用归纳证明法证明:
首先,若存在两个字符串的 L C P = 字 符 串 长 度 LCP = 字符串长度 LCP=字符串长度,那么这两个字符串匹配一定是最优.关于这两个字符串的匹配,不会有更大的答案(因为他们本身就这么长了).所以第一步匹配完所有的 L C P = 字 符 串 长 度 LCP = 字符串长度 LCP=字符串长度的字符串.继续该过程.
…假设枚举到 L C P = i LCP = i LCP=i,假设这两个字符串是 S , T S,T S,T.
由于 L C P = { i + 1 , . . . , m a x l e n } LCP = \{i+1,...,maxlen\} LCP={ i+1,...,maxlen}的情况在这一步之前就处理完了.所以不会存在一对字符串 X , Y X,Y X,Y使得他们和 S , T S,T S,T匹配构成更优解.即这两个匹配是当前最优的.
这个确定了以后,就可以将所有的字符串插入到Trie树.尽量在深度深的地方匹配.维护一个pos[2][maxn]代表树上每个节点所经过的所有字符串的编号.(注意,这里空间复杂度只会到达 字 符 集 长 度 字符集长度 字符集长度)然后用used[2][maxn]代表某个点是否已经在更深的地方匹配.
然后暴力dfs遍历全Tire树.优先在深度深的地方匹配两堆字符串.并将其打上used标记即可.代码较精简.
#include<bits/stdc++.h>
using namespace std;
const int maxn = 800005;
vector <int> pos[2][maxn];
bool vis[2][100005];
int res[100005];
int ans = 0;
namespace Trie{
int tr[maxn][26] , v[maxn] , tot;
void add (char * s , int t , int id){
int u = 0; pos[t][u].push_back(id);
for (int i = 1 ; s[i] ; i++){
if (!tr[u][s[i] - 'a']) tr[u][s[i] - 'a'] = ++tot;
u = tr[u][s[i] - 'a'];
pos[t][u].push_back(id);
}
}
}
void dfs (int u , int dep)
{
using namespace Trie;
for (int i = 0 ; i < 26 ; i++) if (tr[u][i]) dfs (tr[u][i] , dep + 1);
vector<int> now[2];
for (auto g : pos[0][u]) if (!vis[0][g]) now[0].push_back(g);
for (auto g : pos[1][u]) if (!vis[1][g]) now[1].push_back(g);
int cnt = min (now[0].size() , now[1].size());
ans += cnt * dep;
for (int i = 0 ; i < cnt ; i++) {
res[now[0][i]] = now[1][i];
vis[0][now[0][i]] = vis[1][now[1][i]] = true;
}
return ;
}
char tmp[maxn];
int main()
{
ios::sync_with_stdio(false);
int n; cin >> n;
for (int i = 1; i <= n ; i++){
cin >> (tmp + 1);
Trie::add(tmp , 0 , i);
}
for (int i = 1; i <= n ; i++){
cin >> (tmp + 1);
Trie::add(tmp , 1 , i);
}
dfs (0 , 0);
cout << ans << endl;
for (int i = 1; i <= n ; i++){
cout << i << " " << res[i] << endl;
}
return 0;
}