zoukankan      html  css  js  c++  java
  • UVA

    /*
      法一借鉴自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;
    }


  • 相关阅读:
    大数加法、乘法实现的简单版本
    hdu 4027 Can you answer these queries?
    zoj 1610 Count the Colors
    2018 徐州赛区网赛 G. Trace
    1495 中国好区间 尺取法
    LA 3938 动态最大连续区间 线段树
    51nod 1275 连续子段的差异
    caioj 1172 poj 2823 单调队列过渡题
    数据结构和算法题
    一个通用分页类
  • 原文地址:https://www.cnblogs.com/mofushaohua/p/7789416.html
Copyright © 2011-2022 走看看