/* 法一借鉴自bolg:http://blog.csdn.net/code4101/article/details/39587211 收获: 1. typedef定义数组类型,格式应为 typedef int int_array[80]; 此后int_array可作为有80个元素的int数组的简写 http://blog.csdn.net/huang_xw/article/details/8273655 2. 几个好用的define宏定义,简化代码 #define rep(i,b) for(int i=0; i<(b); i++) #define foreach(i,a) for(__typeof((a).begin()) i=a.begin(); i!=(a).end(); ++i) ***注意:__typeof函数在英文前面,是2个_,不是一个_ 同时学到了_typeof关键字的使用 http://blog.csdn.net/cx132123/article/details/6641735 3. 逗号表达式的返回值 概括就是:在C++中,逗号表达式有时候是一种很有用的工具:(A,B,C),则从左到右求值,最后C的(返回)值作为整个表达式的值 http://blog.csdn.net/songzitea/article/details/52177294 4. 还有就是,由于文章输入用的是getline,getline是能读取空串(整个串只有一个回车符)的,所以务必记得,输入n、m两个数字之后,都有getchar(),否则,轻则PE,重则WA */
#include <iostream> #include <cstdio> #include <string> #include <map> #include <cstring> #include <cctype> //字母的判断和转换,用<cctype>里的 isalpha 和 tolower,可以极大简化代码 #include <sstream> using namespace std; #define rep(i, n) for (int i = 0; i < (n); i++) #define foreach(i, a) for (__typeof( (a).begin() )i = a.begin(); i != (a).end(); i++) //这个头文件对STL类的,都很实用,学习了 #define FOR for(int j = limit[i]; j < limit[i + 1]; j++) //遍历同一篇文章的所有行数 const int maxn = 1500 + 5; typedef bool Bit[maxn]; int n, m, lines; // n:文档数,m:请求数,lines:文章行数 int limit[105]; //limit[i]记录第i篇文章从哪一行开始 string doc[maxn]; //储存文章 map<string, Bit> Index; // Index 的 key位单词,Bit[i]为true,表示标记key对应的单词在第一行出现过 //将读取的某行做处理,分割为一个个单词,以记录某个单词在哪一行出现过 void update(string s, int p) { string word; foreach(it, s) { if (isalpha(*it)) *it = tolower(*it); else *it = ' '; } stringstream ss(s); while (ss >> word) Index[word][p] = true; } int main() { cin >> n; getchar();//注意要有一个getchar()吃掉多余的回车,否则后面的那些串,都会恰好错位一行,最终PE rep(i, n) { limit[i] = lines; while (getline(cin, doc[lines]) && doc[lines] != "**********") { update(doc[lines], lines); lines++; } } limit[n] = lines; // 之所以此处要对limit[n]做处理,是因为 rep(i, n) 的宏定义中,i最多能取到(n-1),但 对FOR的宏定义中,有 j < limit[i + 1]这句,i+1的最大可能值,其实是可以取到 n的,所以如果不提前初始化好 limit[n](应初始化为第n篇文章的最后一行的行数+1,如程序中那样初始化),则必定WA string s; //存储请求 Bit mark; //记录哪些行应输出(为了处理每篇文档输出后,还需输出的10个'-') bool *A, *B; cin >> m; getchar(); //注意要有一个getchar()吃掉多余的回车,否则后面的那些串,都会恰好错位一行,最终WA for (int ii = 0; ii < m; ii++ ) { getline(cin, s); if (s[0] == 'N') { A = Index[s.substr(4)]; rep(i, n) { bool flag = true; FOR if (A[j]) //FOR代表遍历当前文章的所有行,找是否出现指定关键字,若出现,则不输出该文章 { flag = false; break; } FOR mark[j] = flag; //如果flag仍为true,说明整篇文章都没有给定的关键字,按照题目要求,该文章的所有行都要输出;否则,该篇文章全部不输出 } } else if (s.find("AND") != string::npos) { int pos = s.find(" AND "); //分离出所要找的两个关键词 A = Index[s.substr(0, pos)]; B = Index[s.substr(pos + 5)]; memset(mark, 0, sizeof(mark)); bool hasA, hasB; //分别在同一文章中,两个关键词是否都出现,必须两者都为true时,该行才可被标记为可输出 rep(i, n) { hasA = hasB = false; //默认没出现 FOR if (A[j]) { hasA = true; break; // 遍历doc[i]这篇文章的所有行数,确定这篇文章中有无关键词A,有只需有一个(故可以用 break),但没有需要所有行全没有,对B同理 } FOR if (B[j]) { hasB = true; break; } if (!(hasA && hasB)) continue; //若要输出,首先这篇文章必须同时包含A和B FOR mark[j] = (A[j] || B[j]); //按照题意,输出是输出至少包含一个关键字的,所以对于文章中已经同时出现A、B的情况以后,对于这篇文章要输出哪些行,只要是有至少一个关键词的行,都满足输出条件 } } else if ( s.find("OR") != string::npos ) { int pos = s.find(" OR "); A = Index[s.substr(0, pos)]; B = Index[s.substr(pos + 4)]; rep(i, lines) mark[i] = (A[i] || B[i]); //注意对OR的处理,就并不需要按照文章遍历这步了,因为最终输出的,也就是出现至少一个关键词的行,所以,可以直接标记这些行,从这个角度,OR比AND和NOT这两种情况,可谓简单许多 } else { memcpy(mark, Index[s], sizeof(mark)); } bool nowOut = false, hasOut = false; //nowOut标记当前是否需要输出10个连续-(此后简称“分隔符”),如果上一篇文章的查询有结果,即hasOut为true,自然应该输出;但是也要注意,分隔符只输出一次(在下一篇文章待输出的查询结果之前输出),所以要用两个bool变量做好控制 //nowOut表示当前是否需要输出分隔符 //hasOut表示当前(/上篇)文章有无查询结果输出,无输出则有一次输出分隔符的机会,在输出下一篇有输出结果的文章,的查询结果前输出(如果接下来几篇都无查询结果,分隔符就一直不输出) //一旦输出分隔符后,nowOut自然也应置false //同时,这种设定下,可以发现的是,如果n篇文章全都没有查询结果,那么hasOut和nowOut都为false,而只要某篇文章中有本次查询需要的结果,则两者必定不会都为false,可用这点判断是否输出 "Sorry, I found nothing." rep(i, n) { if (hasOut) nowOut = true; hasOut = false; FOR if (mark[j]) { if (nowOut) { cout << "----------" << endl; nowOut = false; } cout << doc[j] << endl; hasOut = true; //表示这篇文章有输出结果 } } if (!hasOut && !nowOut) cout << "Sorry, I found nothing." << endl; cout << "==========" << endl; } return 0; }
/* 法二借鉴自: http://blog.csdn.net/XieNaoban/article/details/52628860 和 https://github.com/morris821028/UVa/blob/master/temp/1597%20-%20Searching%20the%20Web.cpp 收获: 1. 如果对map的value,将其引用赋值给value的同类型,会比赋value值(map[key])的耗时少。 因为如果只是赋值,此后每次对value的操作,都需要从map里面重新查找。而如果赋引用,相当于是一劳永逸,只用查找一次,减少了STL的使用对程序速度的影响 详细的说法(本来写在注释中,现在单独剪切到收获中) set<int> &t = word_line[com[0]]; 按照blog: http://blog.csdn.net/XieNaoban/article/details/52628860 中的分析,此处的引用,可谓是这个方法的神来之笔,使得运行速度大大减少 为什么能提高效率? 我的猜测: 按照RLJ曾在《入门经典》P123提到的:虽然map每次插入、查找和删除时间,和元素个数的对数呈线性关系;虽然map每次插入、查找和删除时间,和元素个数的对数呈线性关系; 但是,在一些对时间要求非常高的题目中,STL是有性能方面的瓶颈 而用LRJ的这两句总结,来分析这题,如果t前面不用引用,那么每次调用t时,都要从map中找到对应的单词应对应的集合,虽然map的查找尚算高效的了,但是,该代码在后来多次用到了t,只有将t设置为引用,才可以真正减少每次查找的时间消耗 实在巧妙!应该好好学习下这个小技巧,可在卡TLE的时候思索,怎么降低STL对速度的影响 2. 有关set的详细总结: http://blog.csdn.net/howardemily/article/details/53573427 3. 十分保险的清楚缓存的方式 while (getchar() != ' '); 这种情况下,不仅清除掉多余的空格,甚至如果空格之前有多余的空白字符,也一并清除掉,使得不影响下一次的有效输入 4. 利用位运算的与和或,代替逻辑运算的与和或 这点不详细解释了,注意观察代码中out的定义方式,和更改方式(就是有out的地方,都重点关注一下,就能发现是怎么用位运算代替逻辑运算的) 但是要注意:我猜博主之所以要这样代替,是因为,有 |= 和 &= 运算符,但是,&&= 和 ||= 运算符是不存在的,所以是为了书写的简化了 换成逻辑运算我试过了,也没问题,但是此时,就必须把 out放到有值中,一起进行逻辑与/逻辑或了 */
#include <iostream> #include <vector> #include <set> #include <map> #include <cctype> #include <string> #include <sstream> #define foreach(i, a) for (__typeof( (a).begin() )i = a.begin(); i != (a).end(); i++) #define rep(i, n) for(int i = 0; i < (n); i++) const int INF = 0x3f3f3f3f; using namespace std; map<string, set<int> > word_line; //表示特定单词,在哪些行数中出现过,行数存入一个集合,作为value键 vector<string> doc; int limit[105]; int n, m, line; //分别依次为:文章数、指令数、当前所输入的行的行序号 void storeInfo(string s, int line) { string word; foreach(i, s) { if (isalpha(*i)) *i = tolower(*i); else *i = ' '; } stringstream ss(s); while (ss >> word) word_line[word].insert(line); } vector<string> _command(string s) { string word; vector<string> temp; foreach(i, s) { if (isalpha(*i)) *i = tolower(*i); else *i = ' '; } stringstream ss(s); while (ss >> word) temp.push_back(word); return temp; } int main() { string str; line = 0; cin >> n; while (getchar() != ' '); rep(i, n) { limit[i] = line; //记录每篇文章的起始行 while (getline(cin, str) && str != "**********") { doc.push_back(str); storeInfo(str, line); line++; } } limit[n] = line; //边界处理 cin >> m; while (getchar() != ' '); rep(i, m) { getline(cin, str); vector<string> com = _command(str); int hasOut = 0; //分隔两篇文章中的输出 if (com.size() == 1) // [word] { set<int> &t = word_line[com[0]]; set<int>::iterator it = t.begin(); //注意,it是t的迭代器,也就是出现了该单词的迭代器,其对应的元素,都是含有该单词的行数的集合,所以可以直接输出对应行(doc[*it]),只要注意不同文章之间要有的分隔符即可 rep(j, n) { //在确认单词对应的所在行数的集合不为空的情况下,迭代器不断自增,直到找到属于该篇文章的范围 while (it != t.end() && *it < limit[j]) it++; //不为空且该关键词的确在这篇文章中出现过,才会有输出 if (it != t.end() && *it < limit[j + 1]) { if (hasOut) cout << "----------" << endl; hasOut = 1; while (it != t.end() && *it < limit[j + 1]) { cout << doc[*it] << endl; it++; } } } } else if (com.size() == 2) // NOT [word] { set<int> &t = word_line[com[1]]; set<int>::iterator it = t.begin(); rep(j, n) { //首先先定位到当前文章的范围(因为t中存储的是所有有该单词的行数,可是这些行分布在不同的文章中) while (it != t.end() && *it < limit[j]) it++; //如果it在所有文章中都没出现过,自然在当前遍历的文章中也没出现过,这种情况下,前一个循环中,迭代器it不会自增,所以要单独提出这种情况来考虑 //而后一种,*it >= ...的情况,则说明这个单词在别的文章出现过,但是在这篇没有;所以我们在试图定位的时候,居然定位到了后面的文章中,说明这篇文章确实无该关键词,应该全部输出 if (it == t.end() || *it >= limit[j + 1]) { if (hasOut) cout << "----------" << endl; hasOut = 1; for (int k = limit[j]; k < limit[j + 1]; k++) cout << doc[k] << endl; } } } else if (com.size() == 3) // [word] AND / OR [word] { set<int> &s1 = word_line[com[0]]; set<int> &s2 = word_line[com[2]]; set<int>::iterator it = s1.begin(); set<int>::iterator jt = s2.begin(); rep(j, n) { while (it != s1.end() && *it < limit[j]) it++; while (jt != s2.end() && *jt < limit[j]) jt++; int out; if (com[1] == "and") //AND要求这两个关键词,都在文章中出现过 { out = 1; out &= ( it != s1.end() ) && ( *it < limit[j + 1]); out &= ( jt != s2.end() ) && ( *jt < limit[j + 1]); } else//OR要求这两个关键词,只要有一个出现过即可 { out = 0; out |= ( it != s1.end() ) && ( *it < limit[j + 1]); out |= ( jt != s2.end() ) && ( *jt < limit[j + 1]); } if (!out) continue;//如果这篇文章查询结果无输出,则转而判断下一篇文章 if (hasOut) cout << "----------" << endl; hasOut = 1; while (1) { int a = INF, b = INF; if (it != s1.end() && *it < limit[j + 1]) a = *it; if (jt != s2.end() && *jt < limit[j + 1]) b = *jt; if (a == b && a == INF) break; //此句不可漏掉,否则RE;它是为了判断,何时才能退出输出的循环; //如果前面的两个if,都没有给a或b改值,它们两的值都还是初始化为的INF,说明剩余的行,都不满足至少有一个关键词的输出要求 if (a < b) it++;//如果第一个关键词出现的行数在前,不需改a,且将遍历第一个关键词所有出现行的迭代器,it,自增 else if (a > b) jt++, a = b;//因为最终输出的是第a行,所以赋b给a else it++, jt++;//两者相等就不必改a了,不过两个迭代器都有必要变动 cout << doc[a] << endl; } } } if (!hasOut) cout << "Sorry, I found nothing." << endl; cout << "==========" << endl; } return 0; }