2024年 第33次CCF计算机软件能力认证 202403-2 相似度计算
原题链接:相似度计算
时间限制: 1.0 秒
空间限制: 512 MiB
题目背景
两个集合的 Jaccard 相似度定义为:𝑆𝑖𝑚(𝐴,𝐵)=∣𝐴∩𝐵∣/∣𝐴∪𝐵∣即交集的大小除以并集的大小。当集合 𝐴 和 𝐵完全相同时,𝑆𝑖𝑚(𝐴,𝐵)=1取得最大值;当二者交集为空时,𝑆𝑖𝑚(𝐴,𝐵)=0取得最小值。
题目描述
除了进行简单的词频统计,小 P 还希望使用 Jaccard 相似度来评估两篇文章的相似性。 具体来说,每篇文章均由若干个英文单词组成,且英文单词仅包含“大小写英文字母”。 对于给定的两篇文章,小 P 首先需要提取出两者的单词集合 𝐴和 𝐵,即去掉各自重复的单词。 然后计算出:
- ∣𝐴∩𝐵∣,即有多少个不同的单词同时出现在两篇文章中;
- ∣𝐴∪𝐵∣,即两篇文章一共包含了多少个不同的单词。
最后再将两者相除即可算出相似度。 需要注意,在整个计算过程中应当忽略英文字母大小写的区别,比如 the
、The
和 THE
三者都应被视作同一个单词。
试编写程序帮助小 P 完成前两步,计算出 ∣𝐴∩𝐵∣ 和 ∣𝐴∪𝐵∣;小 P 将亲自完成最后一步的除法运算。
输入格式
从标准输入读入数据。
输入共三行。
输入的第一行包含两个正整数 𝑛 和 𝑚,分别表示两篇文章的单词个数。
第二行包含空格分隔的 𝑛 个单词,表示第一篇文章;
第三行包含空格分隔的 𝑚 个单词,表示第二篇文章。
输出格式
输出到标准输出。
输出共两行。
第一行输出一个整数 ∣𝐴∩𝐵∣,即有多少个不同的单词同时出现在两篇文章中;
第二行输出一个整数 ∣𝐴∪𝐵∣,即两篇文章一共包含了多少个不同的单词。
样例1输入
3 2
The tHe thE
the THE
样例1输出
1
1
样例1解释
𝐴=𝐵=𝐴∩𝐵=𝐴∪𝐵= {the}
样例2输入
9 7
Par les soirs bleus dete jirai dans les sentiers
PICOTE PAR LES BLES FOULER LHERBE MENUE
样例2输出
2
13
样例2解释
𝐴=A= {bleus, dans, dete, jirai, les, par, sentiers, soirs}
∣𝐴∣=8
𝐵=B= {bles, fouler, les, lherbe, menue, par, picote}
∣𝐵∣=7
𝐴∩𝐵=A∩B= {les, par}
∣𝐴∩𝐵∣=2
样例3输入
15 15
Thou that art now the worlds fresh ornament And only herald to the gaudy spring
Shall I compare thee to a summers day Thou art more lovely and more temperate
样例3输出
4
24
子任务
80% 的测试数据满足:𝑛,𝑚≤100且所有字母均为小写;
全部的测试数据满足:𝑛,𝑚≤104 且每个单词最多包含 10 个字母。
解题思路
1.对这种有关联的数据,我喜欢用结构体来存储,开两个结构体数组,分别存储第一行单词a[i].s和第二行单词b[i].s。把每个单词都视为一个字符串进行输入。
2.题目指出整个计算过程中应当忽略英文字母大小写的区别,比如 the
、The
和 THE
三者都应被视作同一个单词。故而我们对输入的单词做一个统一化处理,统一转化为大写或小写,方便后面的运算,我这里是一边输入一边转成了小写。
3.去掉每行重复的单词,分别存到a[i].s1和b[i].s2中,同时开两个字符串把所有不重复的单词串成_aa_bb_cc_dd_这样的形式,方便后续利用find函数直接查找一个单词是否在字符串中。
查找子字符串str是否在字符串s中:利用string类find函数 格式为:s.find(str,开始位置),有则返回第一个匹配的下标,没有则返回-1.
4. ∣𝐴∪𝐵∣,即两篇文章一共包含了多少个不同的单词 = |A| + |B| -∣𝐴∩𝐵∣。
AC代码
#include<bits/stdc++.h>
using namespace std;
const int N = 10010;
int n, m;
struct x{
string s;
string s1;
}a[N];
struct y{
string s;
string s2;
}b[N];
bool cmp1(x a, x b)
{
return a.s < b.s;
}
bool cmp2(y a, y b)
{
return a.s < b.s;
}
string change(string s)
{
int l = s.size();
for(int i = 0; i < l ; i ++) if(s[i] < 'a') s[i] += 32;
return s;
}
int main()
{
cin >> n >> m;
for(int i = 0; i < n; i ++) //输入第一行
{
cin >> a[i].s, a[i].s = change(a[i].s);
}
for(int i = 0; i < m; i ++) //输入第二行
{
cin >> b[i].s, b[i].s = change(b[i].s);
}
sort(a, a+n, cmp1);//把第1行的单词进行排序
sort(b, b+m, cmp2);//把第2行的单词进行排序
string sum1 = "_", sum2 = "_";
//s1、s2里装的是每行里不重复的单词
//sum1、sum2是把这些单词累积串成一个字符串 方便后续查找单词
a[0].s1 = a[0].s, sum1 += a[0].s1 + '_';
b[0].s2 = b[0].s, sum2 += b[0].s2 + '_';
int l1 = 1, l2 = 1; //设置第一行、第二行的单词初始长度
if(a[0].s != a[n-1].s) //因为排过序了 所以首尾单词相同就相当于是只有一个“有效”单词
{
for(int i = 1; i < n; i ++)
{ //去掉重复单词
if(a[i].s != a[i-1].s)
{
a[l1++].s1 = a[i].s, sum1 += a[i].s + '_';
}
}
}
if(b[0].s != b[m-1].s)
{
for(int i = 1; i < m; i ++)
{
//去掉重复单词
if(b[i].s != b[i-1].s)
{
b[l2++].s2 = b[i].s, sum2 += b[i].s + '_';
}
}
}
int ans1 = 0,ans2 = l1 + l2;
if(l1 > l2) //(处理过后的)第1行比第2行长
{
for(int i = 0; i < l2; i ++)
{
//若第2行的单词在第一行可以被找到,则交集+1
//加一个‘_’在单词首尾的目的是作唯一性区分 防止把“the” 和“thee”匹配上这样子
if(sum1.find('_'+b[i].s2+'_', 0) != -1) ans1 ++;
}
}
else
{
for(int i = 0; i < l1; i ++)
{
if(sum2.find('_'+a[i].s1+'_', 0) != -1) ans1 ++;
}
}
cout << ans1 << endl << ans2 - ans1 << endl;
return 0;
}