UVa1597 - Searching the Web

1  题目理解

       刘老师对这题的介绍是这样的:


       看到该题,最容易想到的方法,就是对于每次请求,都去搜索1500行文档的信息。但是这样效率过低,会超过3秒的限制时间。故这题靠LRJ给出的题目要求还不够,还需要读英文原题,因为原题里有这道题的解题思路。以下是我对前四段话的翻译(本人英语四级未过,不对翻译质量负责~~):

“搜索引擎(search engine)”这个名词可能对你来说并不陌生。一般来说,它能在互联网的有效(available)网页(pages)上,提取和组织出信息,并反馈(respond)与用户的问题(queries)关联性(relevant)最大的网页。像全球知名的搜索引擎GOOGLE,是我们上网时重要的工具。如下面这段会话,已成为我们日常生活的一部分:

“像这个词语******是什么意思?”
“额...我也不清楚,谷歌一下吧。”

在这道题中,你需要开发一个小搜索引擎。听起来不可思议,不是吗?不用担心,这里会有向导,来一步步教你如何高效地(efficiently)组织(organize)收集(collection)到的大量文本(texts),对一系列的问题作出快速的回应。你不用关心网页(web page)小程序,所有的网页都会以纯文本(text format)作为输入数据(input data)。而且,提供给你系统的问题集一定是合法(validate)的。

现代搜索引擎使用了一种叫“反向(inversion)”的技术来处理大量的文档。这个方法依赖于一种数据结构(data structure)的构造(construction)方式,叫做反向指针(index),就是标记每个项(term,或者说word)在哪些文档出现过。所有的项组成的集合称为词表(vocabulary),用V来表示(denoted as V)。简单的说,反向指针就是搜索的关键词ω,ω∈V。与其相关的数值是一个指针b(ω),指向一个作为中介(intermediate)的新增数据结构,叫“桶(bucket)”。桶本质(essentially)上就是一个指针列表,标记出所有出现过的文本集。桶的每个条目(entry)就是文档位置信息(document identifier,DID),包含了顺序编号(ordinal number)的某个文档及出现在第几行。

       最关键的是上面的最后一段话,我也是艰难的翻译到这里后,瞬间明白题意了,后面的文章只是举例介绍而已。最后要注意, 输入的多篇文档,行数总和不会超过1500行。我一开始以为是每篇文档不会超过1500行,把这题做的太难了,还悲剧的RE。
       好吧,上面其实都是废话,这一句也是~~~下面步入主题。


2  基本框架


       如果读者想看明白我的解法,建议这里的讲解和代码要配合看。
       (我这里说的文档,就是LRJ说的“文章”的意思)
       既然总行数少,那么重点应该是放在行操作上。但文档的编号还是对解题有影响的,首先程序输出中,每隔一个文档,要输出10个'-';其次对“NOT”,"AND”也有比较大的算法干扰。不过有办法克服的,相信这个思路是的对的,故我先定义一个基本的数据类型typedef bool Bit[1505]。然后定义一个叫Index的map<string, Bit>,以文档中出现的每一个单词为key,对应的值为Bit,Bit[i]=1表示这个单词在i行有出现,否则就是没出现。
       同时,定义变量n为文章总数,lines是文档总行数,m是请求总数。用string doc[1505]来存储每行对应的原始内容。所有的编号都是从0开始。
       关于文章编号的处理,定义int limit[105],limit[i]表示第i篇文档是第几行开始的,有了limit,就能在整个程序中的区分哪些行是哪些文档的,方便对特定文档进行遍历操作。因为整个程序中包含许多这样的操作:将编号为i的文档,遍历其所有的行j,故我把这个for循环写成一个宏定义,简化代码:#define FOR for (int j = limit[i]; j < limit[i + 1]; j++)。


3  解题思路


       分两个大环节,第一个section是:对输入文档信息的初始化操作,包括Index的构建。第二个section是:对每一个请求,给出相应的输出内容。
       第一个环节中,计算出lines,limit都不难。主要是Index的构建:对输入的每行信息(如第p行),要找出里面包含的所有单词,将该单词对应的Bit的第p位记为true(注意Bit建立时每位会自动初始化为false)。这里封装一个函数upDate(string s, int p)来辅助操作,它能将s中的单词对应的Bit的第p位记为true。
       第二个环节中,我引入一个中介变量,Bit mark,mark[j]表示第j行是否需要输出。分两个小节来处理第二环节,第一个subsection得到四种命令中的某种,只需要负责计算出对应的mark,由第二个subsection来负责mark的输出操作。因为这题的输出格式不太容易控制,每输出一个文档要输出一个结束标志,如果对四种命令都写输出语句,会造成很多不必要的重复和代码量过大。
       那么第一个subsection中,如何处理四种命令呢?假设输出的第一个单词对应的Bit是A,第二个单词对应的Bit是B(不一定存在)。那么对于单个单词的查询,mark其实就是A。对于OR操作,只要A与B直接每位进行或操作,就能得到mark,即mark[j] = (A[j]||B[j])。对于NOT操作(比较麻烦),需要使用到前面提到的宏FOR,对每个文档的行进行遍历,如果A[j]的值都是false,代表这篇文章要全部输出,故对应的这块mark[j]都等于true,否则,这块mark[j]都等于false。对于AND操作(最难的一种),也需要对每块单独进行处理,如果这块A和B都有ture的值,代表这篇文档两个单词都有出现,那么这块mark按“OR”同样的方法进行局部赋值,否则这块mark全部等于false。

       讲了这么多,其实我的方法也不是最优的,AC这题用了0.8秒,有个大牛用了0.08秒不知道怎么解的。所以我的方法能不能理解其实关系不大,更重要的是自己思考。我写这么多只是想让以后的自己能看懂~~~


4  求解代码


       这是VJ里AC该题的完整代码: http://vjudge.net/vjudge/problem/viewSource.action?id=2787670,包括了点调试代码。
       然后在博客这边贴一份精简版:
[cpp]  view plain copy
  1. //{头文件模板  
  2. #include <algorithm>  
  3. #include <bitset>  
  4. #include <cassert>  
  5. #include <cmath>  
  6. #include <cstdio>  
  7. #include <cstdlib>  
  8. #include <cstring>  
  9. #include <functional>  
  10. #include <iomanip>  
  11. #include <iostream>  
  12. #include <list>  
  13. #include <map>  
  14. #include <queue>  
  15. #include <set>  
  16. #include <sstream>  
  17. #include <stack>  
  18. #include <string>  
  19. #include <vector>  
  20. using namespace std;  
  21. //}头文件模板  
  22. #define rep(i,b) for(int i=0; i<(b); i++)  
  23. #define foreach(i,a) for(__typeof((a).begin()) i=a.begin(); i!=(a).end(); ++i)  
  24. #define FOR for (int j = limit[i]; j < limit[i + 1]; j++)  
  25. typedef bool Bit[1505];  
  26.   
  27. int n, lines, m;          // n文档数, lines行数, m请求数  
  28. int limit[105];           // limit[i]: 第i篇文档从第几行开始  
  29.   
  30. string doc[1505];         // 存储内容  
  31. map<string, Bit> Index;   // Index[单词]: B标记了哪些行出现过  
  32.   
  33. // 用s来更新Index  
  34. void upDate(string s, int p) {  
  35.     string word;  
  36.     foreach(it, s) {  
  37.         if (isalpha(*it)) *it = tolower(*it); // 变小写  
  38.         else *it = ' '// 非字母变空白  
  39.     }  
  40.   
  41.     stringstream ss(s);  
  42.     while (ss >> word) Index[word][p] = true;  
  43. }  
  44.   
  45. int main() {  
  46.     //{section: 文档数据输入  
  47.     scanf("%d ", &n);  
  48.     rep(i, n) {  
  49.         limit[i] = lines;  
  50.         while (getline(cin, doc[lines]), doc[lines] != "**********") {  
  51.             upDate(doc[lines], lines);  
  52.             lines++;  
  53.         }  
  54.     }  
  55.     limit[n] = lines;//}  
  56.   
  57.     //{section: 对获取的请求输出对应的内容  
  58.     string  s;                      // s存储每次得到的请求  
  59.     Bit     mark;                   //{记录哪些行应该输出  
  60.                                     //}mark的解释: 因为每篇文档要用10个'-'隔开,比较麻烦,最后统一处理  
  61.     bool    *A, *B;  
  62.   
  63.     scanf("%d ", &m);  
  64.     for (int iii = 0; iii < m; iii++) {  
  65.         getline(cin, s);  
  66.   
  67.         //{subsection: 计算出mark中介  
  68.         if (s[0] == 'N') {  
  69.             A = Index[s.substr(4)];  
  70.             rep(i, n) {  
  71.                 bool logo = true;  
  72.                 FOR if (A[j]) { logo = falsebreak; }  
  73.                 FOR mark[j] = logo;  
  74.             }  
  75.         } else if (s.find("AND") != string::npos) {  
  76.             int p = s.find(" AND ");  
  77.             A = Index[s.substr(0, p)];  
  78.             B = Index[s.substr(p + 5)];  
  79.             memset(mark, 0, sizeof(mark));  
  80.             bool hasA, hasB;    // 在同一文章中, 两个词是否都出现  
  81.             rep(i ,n) {  
  82.                 hasA = hasB = false;    // 默认没出现  
  83.                 FOR if (A[j]) { hasA = truebreak; }  
  84.                 FOR if (B[j]) { hasB = truebreak; }  
  85.                 if (!(hasA&&hasB)) continue;  
  86.                 FOR mark[j] = (A[j] || B[j]);  
  87.             }  
  88.         } else if (s.find("OR") != string::npos) {  
  89.             int p = s.find(" OR ");  
  90.             A = Index[s.substr(0, p)];  
  91.             B = Index[s.substr(p + 4)];  
  92.             rep(i, lines) mark[i] = (A[i] || B[i]);  
  93.         } else memcpy(mark, Index[s], sizeof(mark));//}  
  94.   
  95.         //{subsection: 输出mark  
  96.         bool hasOut = false, needOut = false;    // 记录上一轮是否有输出文档  
  97.         rep(i, n) {  
  98.             if (hasOut) needOut = true;  
  99.             hasOut = false;  
  100.             FOR if (mark[j]) {  
  101.                 if (needOut) {  
  102.                     cout << "----------\n";  
  103.                     needOut = false;  
  104.                 }  
  105.                 cout << doc[j] << "\n";  
  106.                 hasOut = true;  
  107.             }  
  108.         }  
  109.         if (!(needOut||hasOut)) cout << "Sorry, I found nothing.\n";  
  110.         cout << "==========\n";//}  
  111.     }//}  
  112.   
  113.     return 0;  
  114. }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值