51nod-1526-贪心+Trie
题目来源: CodeForces
基准时间限制:1 秒 空间限制:131072 KB 分值: 320 难度:7级算法题
收藏
关注
班里有n个同学。老师为他们选了n个笔名。现在要把这些笔名分配给每一个同学,每一个同学分配到一个笔名,每一个笔名必须分配给某个同学。现在定义笔名和真名之间的相关度是他们之间的最长公共前缀。设笔名为a,真名为b,则他们之间的相关度为lcp(a,b)。那么我们就可以得到匹配的质量是每一个同学笔名和真名之间相关度的和。
现在要求分配笔名,使得匹配质量最大。
样例解释:
· bill → bilbo (lcp = 3)
· galya → galadriel (lcp = 3)
· gennady → gendalf (lcp = 3)
· toshik → torin (lcp = 2)
· boris → smaug (lcp = 0)
Input
单组测试数据。 第一行有一个整数n (1≤n≤100000),表示班级中同学的数目。 接下来n行,表示每一个同学的真名,每一个名字是非空串,且由小写字母组成。 名字可能重复。 最后n行是老师已经安排好的笔名。每一个笔名是一个非空串,且由小写字母组成。 笔名可能重复。 输入的字符总数目不超过 800000。
Output
输出最大的匹配质量。
Input示例
样例输入1 5 gennady galya boris bill toshik bilbo torin gendalf smaug galadriel
Output示例
样例输出1 11
很容易想到一个贪心的算法是,对于每一个笔名,把他用在使得他贡献最大的人身上,字符串前缀最大,数据量又很大,考虑使用字典树把人名存下,用笔名在树上匹配。思路是对的但出了一个bug。
如果在某个称号还未配对时,他的最优人被其他人用了咋整。可以这样想,每个结点统计前缀个数vis[u],匹配时只要vis[u]存在就让ans++;累计一个贡献,最后就是正解。假如这个前缀有多个,那就是
按贡献大小给占用他的人,如果个数有限的话,那么势必会有一些名称的匹配贡献是0,我们当然要让匹配的少的那些为零。这个过程在代码里不会呈现但不会影响最终结果。
假如有笔名: abxx abccc 有人名: abccx 有且仅有一个
当abxx先找时,相当于贡献了'a','b'两个贡献,余下的不再匹配树上这两个字符对应节点的价值也会减一(归零),当abccc进行匹配时,会做出'c','c'两个贡献
这样一共进行了四个贡献,虽然看起来abccc前缀并没有完全匹配,但只不过是"abxx"提前替他匹配了一小段前缀而已。
1 #include<bits/stdc++.h> 2 using namespace std; 3 int ch[800010][26]; 4 int sz=1,ans=0; 5 int vis[800010]; 6 char s[800010]; 7 void insert(char *s){ 8 int u=0,n=strlen(s); 9 for(int i=0;i<n;++i){ 10 int c=s[i]-'a'; 11 if(!ch[u][c]) ch[u][c]=sz++; 12 vis[u]++; 13 u=ch[u][c]; 14 } 15 vis[u]++; 16 } 17 void match(char *s){ 18 int i,u=0,n=strlen(s),r=0,ret=0; 19 for(i=0;i<n;++i){ 20 int c=s[i]-'a'; 21 if(ch[u][c]==0) return ; 22 u=ch[u][c]; 23 if(vis[u]){ 24 vis[u]--; 25 ans++; 26 } 27 } 28 } 29 int main() 30 { 31 int n,m,i,j,k; 32 cin>>n; 33 for(i=1;i<=n;++i){ 34 scanf("%s",s); 35 insert(s); 36 } 37 for(i=1;i<=n;++i){ 38 scanf("%s",s); 39 match(s); 40 } 41 printf("%d\n",ans); 42 return 0; 43 } 44 /* 45 5 46 gennady 47 galya 48 boris 49 bill 50 toshik 51 52 bilbo 53 torin 54 gendalf 55 smaug 56 galadriel 57 */