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;
    }


  • 相关阅读:
    利用JavaScript数组动态写入HTML数据节点
    个人项目网站,部分截图
    HTML5 JavaScript API
    简述几项关于web应用的开发技术
    最值得学习的编程语言
    使用Ajax与服务器端通信
    Ajax与用户交互的存储格式JSON
    兄弟连教育分享:用CSS实现鼠标悬停提示的方法
    移动端HTML5性能优化
    兄弟连PHP培训教你提升效率的20个要点
  • 原文地址:https://www.cnblogs.com/mofushaohua/p/7789416.html
Copyright © 2011-2022 走看看