LZW网页判重的题解
百度之星的题目,拿来练练手。
问题背景
有一种简单的网页判重的方法,通过求两个网页内容的最长公共子序列(LCS)长度来判定两个网页的相似程度。如:
(网页A)老师:请用“果然”造句。
(网页B)学生:先吃水果,然后喝汽水……
它们的最长公共子序列为“果然”,长度为2。注意这里的“子序列”并不要求连续。
类似的,下面两个网页:
(网页A)老师:请用“果然”造句。
(网页B)学生:先吃水果,然后喝汽水,果然拉肚子……
最长公共子序列还是“果然”,长度为2。但不难看出,由于“果然”两个字在网页B中也曾连续出现,第二组网页比第一组更加“相似”。为了区分开这两种情况的区分度,我们改用一种称为LZW的理论。为了严格的叙述相似度的计算方法,我们首先定义“文本单元”。
假定网页用一个不包含空白字符(空格、回车换行、水平制表符)的字符串来表示。它只包含纯文本,没有标签。在计算相似度之前,你应该首先对该字符串进行处理,划分成一个个“文本单元”。每个文本单位可以是一个中文字、英文单词(由一个或多个连续的半角英文字母和数字组成,正规表达式为[a-zA-Z0- 9]+)、或者一个标点符号。
根据上述定义,同一个标点符号的全角和半角应该被作为不同的文本单元,尽管他们看起来可能很相近;每个单独全角英文和全角数字都应该被看成一个单独的文本单元,而连续的半角英文字母和数字应被看成一个整体。总之,全角的字符可以与中文字同等对待。
这样,网页被看成文本单元序列。例如,网页“内容?123456??web2.00#”切分出的文本单元序列为(为了显示方便,用下划线分隔各文本单元):
内_容_?_1_2_345_6_?_?_web2_._00_#
而网页“why内容相似??1234567890,web#00”的切分结果为:
why_内_容_相_似_?_?_1234567890_,_web_#_00
黑体部分给出了两个网页的一个公共子序列。注意“内容”、“??”分别在两个网页中都是连续出现的文本单元。为了奖励这种情况,LZW规定一段由连续k个文本单元组成的字符串权值为k^2。在刚才的例子中,“内容”、“??”的权值均为4。但“00”是一个数字串,应当被看成一个单独的文本单元。所以权值仅为1。
根据上述规则,公共子序列“内容 ?? 00”的权值为2^2+2^2+1=9。在所有可能的子序列中,这个权值是最大的。
给定两个网页,求他们的LZW相似度,即所有可能的公共子序列中的最大权值。
注意
1) 输入的网页内容以GBK编码(参见FAQ)
2) 除了大小写英文字母和数字之外的其他半角字符均视为标点符号。
输入格式
包含两行,分别是网页A和B对应的字符串(不包含空白字符)。每行至少包含5个字节,最多包含200个字节。
输出格式
输出仅一行,包含一个整数,为两个网页的LZW相似度。
样例输入
内容?123456??web2.00#
why内容相似??1234567890,web#00
样例输出
9
解答如下:
思路:首先分割串的文本单元,然后用类似LCS的算法。
注意:最后的结果不一定是最长的子串,所以需要用修改版的LCS,将LCS的长度改为权重。
示例:
我我我我我我我我我最大权重文本
最大权重文本我他我他我他我他我他我他我他我
上面两个串的最长子串显然是“我我我我我我我我我”,但是最大相似权重却是“最大权重文本”。
#include <iostream>
#include <string>
#include <vector>
using namespace std;
bool IsEnglish(char a)
{
return (a>='0'&&a<='9') || (a>='a'&&a<='z') ||
(a>='A'&&a<='Z');
}
//将字符串切割成一个个的文本单元
void SplitStr(const string& str,vector<string>& text1)
{
int i,j;
for(i=0;i<str.length();i++)
{
//全角字符
if(str[i]<0)
{
text1.push_back(string(str,i,2));
++i;
}
else
{
//非英文和数字字符
if(!IsEnglish(str[i]))
{
text1.push_back(string(str,i,1));
continue;
}
j=i;
while(IsEnglish(str[i])) ++i;
text1.push_back(string(str,j,i-j));
--i;
}
}
}
int main(int argc, char* argv[])
{
int i,j;
string str1,str2;
vector<string> text1;
vector<string> text2;
cin>>str1>>str2;
SplitStr(str1,text1);
SplitStr(str2,text2);
//samelen记录连续的文本单元次数
int samelen[200][200]={{0}};
//step记录有关相同子串的位置信息
int step[200][200]={0};
//weight[I][J]记录A串前i个文本和B串前j个文本的最大相似权重
int weight[200][200]={0};
for(i=1;i<text1.size()+1;i++)
{
for(j=1;j<text2.size()+1;j++)
{
if(text1[i-1]==text2[j-1])
{
samelen[i][j] = samelen[i-1][j-1] + 1;
step[i][j] = 1;
weight[i][j] = weight[i-1][j-1] +
samelen[i][j]*samelen[i][j] -
samelen[i-1][j-1]*samelen[i-1][j-1];
}
else if(weight[i-1][j]>=weight[i][j-1])
{
weight[i][j]=weight[i-1][j];
//TEXT1 MOVE,TEXT2 STAY
step[i][j]= 2;
}
else
{
weight[i][j]=weight[i][j-1];
//TEXT1 stay,TEXT2 move
step[i][j]=3;
}
}
}
cout<<weight[text1.size()][text2.size()]<<endl;
//以下代码用于输出最大相似权重的文本单元的内容以及它们在两个串中的索引,题目没要求
int index1[200]={0};
int index2[200]={0};
i=text1.size();
j=text2.size();
int same=0;
while(i>0&&j>0)
{
if(step[i][j]==1)
{
index1[same]=i;
index2[same]=j;
same++;
--i;
--j;
}
else if(step[i][j]==2)
{
--i;
}
else if(step[i][j]==3)
{
--j;
}
else
{
printf("error!\n");
}
}
for(i=same-1;i>=0;i--)
{
cout<<text1[index1[i]-1]<<" ";
}
cout<<endl;
return 0;
}