Description
班里有n个同学。老师为他们选了n个笔名。现在要把这些笔名分配给每一个同学,每一个同学分配到一个笔名,每一个笔名必须分配给某个同学。现在定义笔名和真名之间的相关度是他们之间的最长公共前缀。设笔名为a,真名为b,则他们之间的相关度为lcp(a,b)。那么我们就可以得到匹配的质量是每一个同学笔名和真名之间相关度的和。
现在要求分配笔名,使得匹配质量最大。
Solution
牛逼的题目。。
我们把笔名和真名都插进trie里,按照深度从大到小枚举trie上的节点,如果一个节点的子树内两个size都不为0那么就内部互相匹配,然后暴力跳父亲删掉匹配了的数量
每个串至多被删一次,那么复杂度是等于节点数排序+串长之和的
Code
#include <stdio.h>
#include <string.h>
#include <string.h>
#include <algorithm>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)
#define fi first
#define se second
typedef long long LL;
typedef std:: pair <int,int> pair;
const int N=800005;
int rc[N][26],size[N][2],dep[N],fa[N],tot=1;
char str[N];
pair p[N];
void ins(char *s,int r) {
int n=strlen(s+1),x=1;
rep(i,1,n) {
size[x][r]++;
if (!rc[x][s[i]-'a']) {
rc[x][s[i]-'a']=++tot;
dep[rc[x][s[i]-'a']]=dep[x]+1;
fa[tot]=x;
}
x=rc[x][s[i]-'a'];
}
size[x][r]++;
}
int main(void) {
freopen("data.in","r",stdin);
int n; scanf("%d",&n);
rep(i,1,n) {
scanf("%s",str+1);
ins(str,0);
}
rep(i,1,n) {
scanf("%s",str+1);
ins(str,1);
}
rep(i,1,tot) p[i]=pair(-dep[i],i);
std:: sort(p+1,p+tot+1);
LL ans=0;
rep(i,1,tot) {
int d=std:: min(size[p[i].se][0],size[p[i].se][1]);
if (!d) continue;
for (int x=p[i].se;x;x=fa[x]) {
size[x][0]-=d;
size[x][1]-=d;
}
ans-=1LL*d*p[i].fi;
}
printf("%lld\n", ans);
return 0;
}