2008百度之星资格赛第二题——网页判重
问题背景
有一种简单的网页判重的方法,通过求两个网页内容的最长公共子序列(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
解答:
该题主要分两部分,一部分就是解析成文本单元,另一部分就是LZW的实现,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
解答:
该题主要分两部分,一部分就是解析成文本单元,另一部分就是LZW的实现,LZW其实是最长公共子序列算法的一个变形。
//
============================================================================
// Name : LZW.cpp
// Author : Yovn
// Version :
// Copyright : [email protected]
// ============================================================================
#include < iostream >
#include < string >
#include < cstring >
using namespace std;
void parseToLZWLine( const char * in, int inLen, char **& out, int & actualLen) {
int mark = 0 ;
actualLen = 0 ;
out = new char * [inLen];
for ( int i = 0 ; i < inLen; i ++ ) {
char ch = in[i];
if (ch < 0 ) {
if (mark < i) {
out[actualLen] = new char [i - mark + 1 ];
strncpy(out[actualLen], in + mark, i - mark);
out[actualLen][i - mark] = ' \0 ' ;
actualLen ++ ;
}
out[actualLen] = new char [ 3 ];
out[actualLen][ 0 ] = ch;
out[actualLen][ 1 ] = in[ ++ i];
out[actualLen][ 2 ] = ' \0 ' ;
actualLen ++ ;
mark = i + 1 ;
} else if ((ch >= ' a ' && ch <= ' z ' ) || (ch >= ' A ' && ch <= ' Z ' ) || (ch >= ' 0 ' && ch <= ' 9 ' )) {
continue ;
} else // only one case left
{
if (mark < i) {
out[actualLen] = new char [i - mark + 1 ];
strncpy(out[actualLen], in + mark, i - mark);
out[actualLen][i - mark] = ' \0 ' ;
actualLen ++ ;
}
out[actualLen] = new char [ 2 ];
out[actualLen][ 0 ] = ch;
out[actualLen][ 1 ] = ' \0 ' ;
actualLen ++ ;
mark = i + 1 ;
}
}
if (mark < inLen) {
out[actualLen] = new char [inLen - mark + 1 ];
strncpy(out[actualLen], in + mark, inLen - mark);
out[actualLen][inLen - mark] = ' \0 ' ;
actualLen ++ ;
}
}
void printLZWStr( char ** lzwStr, int lzwLen) {
for ( int i = 0 ; i < lzwLen; i ++ ) {
printf( " %s " , lzwStr[i]);
if (i < lzwLen - 1 ) {
printf( " _ " );
}
}
printf( " \n " );
}
void freeLZWStr( char ** lzwStr, int lzwLen) {
for ( int i = 0 ; i < lzwLen; i ++ ) {
delete[] lzwStr[i];
}
delete[] lzwStr;
}
int calcLZW( char ** left, int leftLen, char ** right, int rightLen) {
// printLZWStr(left, leftLen);
// printLZWStr(right, rightLen);
int ** result = new int * [leftLen + 1 ];
int ** len = new int * [leftLen + 1 ];
for ( int i = 0 ; i <= leftLen; i ++ ) {
result[i] = new int [rightLen + 1 ];
len[i] = new int [rightLen + 1 ];
result[i][ 0 ] = 0 ;
len[i][ 0 ] = 0 ;
}
memset(result[ 0 ], 0 , sizeof( int ) * (rightLen + 1 ));
memset(len[ 0 ], 0 , sizeof( int ) * (rightLen + 1 ));
for ( int i = 1 ; i <= leftLen; i ++ ) {
for ( int j = 1 ; j <= rightLen; j ++ ) {
if (strcmp(left[i - 1 ], right[j - 1 ]) == 0 ) {
// back trace one
len[i][j] = len[i - 1 ][j - 1 ] + 1 ;
result[i][j] = result[i - 1 ][j - 1 ] - (len[i - 1 ][j - 1 ] * len[i - 1 ][j - 1 ])
+ (len[i][j] * len[i][j]);
} else if (result[i - 1 ][j] >= result[i][j - 1 ]) {
result[i][j] = result[i - 1 ][j];
} else {
result[i][j] = result[i][j - 1 ];
}
}
}
int ret = result[leftLen][rightLen];
for ( int i = 0 ; i <= leftLen; i ++ ) {
delete[] result[i];
delete[] len[i];
}
delete[] result;
delete[] len;
return ret;
}
int main() {
char ** lzwStr1 = NULL;
char ** lzwStr2 = NULL;
string str1;
string str2;
int lzwLen1 = 0 ;
int lzwLen2 = 0 ;
getline(cin,str1);
getline(cin,str2);
parseToLZWLine(str1.c_str(), strlen(str1.c_str()), lzwStr1, lzwLen1);
parseToLZWLine(str2.c_str(), strlen(str2.c_str()), lzwStr2, lzwLen2);
cout << calcLZW(lzwStr1, lzwLen1, lzwStr2, lzwLen2) << endl;
freeLZWStr(lzwStr1, lzwLen1);
freeLZWStr(lzwStr2, lzwLen2);
return 0 ;
}
// Name : LZW.cpp
// Author : Yovn
// Version :
// Copyright : [email protected]
// ============================================================================
#include < iostream >
#include < string >
#include < cstring >
using namespace std;
void parseToLZWLine( const char * in, int inLen, char **& out, int & actualLen) {
int mark = 0 ;
actualLen = 0 ;
out = new char * [inLen];
for ( int i = 0 ; i < inLen; i ++ ) {
char ch = in[i];
if (ch < 0 ) {
if (mark < i) {
out[actualLen] = new char [i - mark + 1 ];
strncpy(out[actualLen], in + mark, i - mark);
out[actualLen][i - mark] = ' \0 ' ;
actualLen ++ ;
}
out[actualLen] = new char [ 3 ];
out[actualLen][ 0 ] = ch;
out[actualLen][ 1 ] = in[ ++ i];
out[actualLen][ 2 ] = ' \0 ' ;
actualLen ++ ;
mark = i + 1 ;
} else if ((ch >= ' a ' && ch <= ' z ' ) || (ch >= ' A ' && ch <= ' Z ' ) || (ch >= ' 0 ' && ch <= ' 9 ' )) {
continue ;
} else // only one case left
{
if (mark < i) {
out[actualLen] = new char [i - mark + 1 ];
strncpy(out[actualLen], in + mark, i - mark);
out[actualLen][i - mark] = ' \0 ' ;
actualLen ++ ;
}
out[actualLen] = new char [ 2 ];
out[actualLen][ 0 ] = ch;
out[actualLen][ 1 ] = ' \0 ' ;
actualLen ++ ;
mark = i + 1 ;
}
}
if (mark < inLen) {
out[actualLen] = new char [inLen - mark + 1 ];
strncpy(out[actualLen], in + mark, inLen - mark);
out[actualLen][inLen - mark] = ' \0 ' ;
actualLen ++ ;
}
}
void printLZWStr( char ** lzwStr, int lzwLen) {
for ( int i = 0 ; i < lzwLen; i ++ ) {
printf( " %s " , lzwStr[i]);
if (i < lzwLen - 1 ) {
printf( " _ " );
}
}
printf( " \n " );
}
void freeLZWStr( char ** lzwStr, int lzwLen) {
for ( int i = 0 ; i < lzwLen; i ++ ) {
delete[] lzwStr[i];
}
delete[] lzwStr;
}
int calcLZW( char ** left, int leftLen, char ** right, int rightLen) {
// printLZWStr(left, leftLen);
// printLZWStr(right, rightLen);
int ** result = new int * [leftLen + 1 ];
int ** len = new int * [leftLen + 1 ];
for ( int i = 0 ; i <= leftLen; i ++ ) {
result[i] = new int [rightLen + 1 ];
len[i] = new int [rightLen + 1 ];
result[i][ 0 ] = 0 ;
len[i][ 0 ] = 0 ;
}
memset(result[ 0 ], 0 , sizeof( int ) * (rightLen + 1 ));
memset(len[ 0 ], 0 , sizeof( int ) * (rightLen + 1 ));
for ( int i = 1 ; i <= leftLen; i ++ ) {
for ( int j = 1 ; j <= rightLen; j ++ ) {
if (strcmp(left[i - 1 ], right[j - 1 ]) == 0 ) {
// back trace one
len[i][j] = len[i - 1 ][j - 1 ] + 1 ;
result[i][j] = result[i - 1 ][j - 1 ] - (len[i - 1 ][j - 1 ] * len[i - 1 ][j - 1 ])
+ (len[i][j] * len[i][j]);
} else if (result[i - 1 ][j] >= result[i][j - 1 ]) {
result[i][j] = result[i - 1 ][j];
} else {
result[i][j] = result[i][j - 1 ];
}
}
}
int ret = result[leftLen][rightLen];
for ( int i = 0 ; i <= leftLen; i ++ ) {
delete[] result[i];
delete[] len[i];
}
delete[] result;
delete[] len;
return ret;
}
int main() {
char ** lzwStr1 = NULL;
char ** lzwStr2 = NULL;
string str1;
string str2;
int lzwLen1 = 0 ;
int lzwLen2 = 0 ;
getline(cin,str1);
getline(cin,str2);
parseToLZWLine(str1.c_str(), strlen(str1.c_str()), lzwStr1, lzwLen1);
parseToLZWLine(str2.c_str(), strlen(str2.c_str()), lzwStr2, lzwLen2);
cout << calcLZW(lzwStr1, lzwLen1, lzwStr2, lzwLen2) << endl;
freeLZWStr(lzwStr1, lzwLen1);
freeLZWStr(lzwStr2, lzwLen2);
return 0 ;
}