题意分析:
(1)给出每个课程的编号以及选课的人数和选课的学生的姓名,给出若干个同学的姓名,要求按照给出的顺序列出每个学生选定的课程数、课程编号(按照从小到大的顺序)
(2)题目意思很简单,在做的过程中,可能会有多种方案,相信大家大部分人前面几个案例都可以过,就是最后一个案例要么超时,要么内存超限。因为涉及到根据字符串来索引列表,并且对索引到的结果排序。根据此思路就会出现三种做法,每一次都会从算法效率的瓶颈入手,进行一次次改进,来演示如何规范地去解一道编程题的步骤。希望对大家有所启发。
使用容器map<string,vector<int> >mp;输出时,对索引的结果直接排序后输出
#include <iostream>
#include <vector>
#include <map>
#include <string.h>
#include <algorithm>
using namespace std;
map<string,vector<int> >mp;
int main()
{
int N,K,index,num,i=0;
string name;
cin>>N>>K;
while(i<K)
{
cin>>index>>num;
int j=0;
while(j<num)
{
cin>>name;
mp[name].push_back(index);
j++;
}
i++;
}
for(int i=0;i<N;i++)
{
cin>>name;
cout<<name<<" "<<mp[name].size();
sort(mp[name].begin(),mp[name].end());
vector<int>::iterator it=mp[name].begin();
for(;it!=mp[name].end();it++)cout<<" "<<(*it);
cout<<endl;
}
return 0;
}
该算法会导致最后一个案例超时,我们很自然怀疑是因为排序限制了算法的效率。因此可以考虑如果要避免排序,可以考虑在输入时就让学生的选课表天然有序,这样输出学生的课程时,课程序号也就天然有序了,就必须先用map<int,vector<string>>mp将课程的信息保存后,再按照课程序号依次输入
#include <iostream>
#include <vector>
#include <map>
#include <string.h>
using namespace std;
map<string,vector<int> >mp;
map<int,vector<string> >course;
int main()
{
int N,K,index,num,i=0;
string name;
cin>>N>>K;
while(i<K)
{
cin>>index>>num;
int j=0;
while(j<num)
{
cin>>name;
course[index].push_back(name);
j++;
}
i++;
}
for(int i=1;i<=K;i++)
{
vector<string>::iterator ite=course[i].begin();
for(;ite!=course[i].end();ite++)
{
mp[*ite].push_back(i);
}
}
for(int i=0;i<N;i++)
{
cin>>name;
cout<<name<<" "<<mp[name].size();
vector<int>::iterator it=mp[name].begin();
for(;it!=mp[name].end();it++)cout<<" "<<(*it);
cout<<endl;
}
return 0;
}
很遗憾,这次依然是超时,我们分析原因,可能还是因为我们做了一次课程信息的转存限制了算法效率,看来,此路行不通,能否不通过转存课程信息,而通过改进对学生课程序号排序呢,于是我们想到了位图排序。直接使用bool course[2501]来标识学生选了哪些课程,这样以牺牲空间来提高时间。
#include <iostream>
#include <vector>
#include <map>
#include <string.h>
using namespace std;
struct stu
{
bool course[2501];
int total;
};
map<string, stu>mp;
int main()
{
int N,K,index,num,i=0;
string name;
cin>>N>>K;
while(i<K)
{
cin>>index>>num;
int j=0;
while(j<num)
{
cin>>name;
mp[name].course[index]=1;
mp[name].total++;
j++;
}
i++;
}
for(int i=0;i<N;i++)
{
cin>>name;
cout<<name<<" "<<mp[name].total;
for(int j=1;j<=K;j++)
{
if(mp[name].course[j])cout<<" "<<j;
}
cout<<endl;
}
return 0;
}
然而,上天并没有眷顾我们,牺牲空间的代价就是最后一个案例超出内存限制了。看来此时,我们已经无计可施。只能痛苦地再去看看题目了,等等!学生的名字好奇特,三个大写字母加一个数字,很固定的格式。这么巧合,看来这样的安排并不是空穴来风,否则随便给一个名字字符串不就好了,这可能就是给我在处理索引的时候提供了某些线索。此时我们再细想,我们一直青睐的map容器对字符串的索引其实也是效率不高的,不如直接使用位图索引效率高。由此我们推测:对字符串的索引限制了算法的效率。要改进索引策略,就只能对姓名进行编码了。最简单的编码就是将其按照“N进制”转换了。很显然这里的前三位进制为26,后一位为十。除了这点,其他的不变
#include <iostream>
#include <vector>
#include <map>
#include <string.h>
#include <algorithm>
#include <stdio.h>
using namespace std;
const int num=26*26*26*10+1;
vector<int>stu[num];
int hashName(string name)
{
int len=name.length();
int res=0;
for(int i=0;i<len-1;i++)
{
res=res*26+name[i]-'A';
}
res=res*10+name[3]-'0';
return res;
}
int main()
{
int N,K,index,num,hname,i=0;
char name[5];
cin>>N>>K;
while(i<K)
{
cin>>index>>num;
int j=0;
while(j<num)
{
cin>>name;
hname=hashName(name);
stu[hname].push_back(index);
j++;
}
i++;
}
for(int i=0;i<N;i++)
{
cin>>name;
hname=hashName(name);
cout<<name<<" "<<stu[hname].size();
sort(stu[hname].begin(),stu[hname].end());
vector<int>::iterator itb=stu[hname].begin();
vector<int>::iterator ite=stu[hname].end();
for(;itb!=ite;itb++)cout<<" "<<*itb;
cout<<endl;
}
return 0;
}
到此,如果你是惯用C语言的小伙伴,那么恭喜你,你已经成功AC了。如果是C++的小伙伴,那么对不起,上天仍然没有眷顾你,最后一个案例仍然超时。Why?难道同一套语言写出来的算法会有这么大效率诧异?
当然是!!!何况C++是基于C的升级版也不例外。这里要说到C语言中的标准输入输出scanf、printf和C++中标准输入输出流cin、cout其实在效率上来讲,scanf比cin的效率高出近10倍之多,有些输入数据量比较大的情况下,使用cin还没等输入完已经超时了。这是由于C++对标准输入做了封装,这里面的解释是:
(1)默认情况,cin与stdin总是保持同步的,也就是说这两种方法可以混用,而不必担心文件指针混乱,同时cout和stdout也一样,两者混用不会输 出顺序错乱。正因为这个兼容性的特性,导致cin有许多额外的开销,如何禁用这个特性呢?只需一个语句 std::ios::sync_with_stdio(false);,这样就可以取消cin于stdin的同步了,此时的cin就与scanf差不多了。
(2)另一种解释: cout在输出时总是要先将输出的存入缓存区。而printf直接调用系统进行IO,它是非缓存的。所以cout比printf慢。
终于我们搞明白了原因。再次改一下程序,此外,为了优化程序,将输出循环体中的stu[hname].end()直接拿到外面去,这样就不用每次判断都要算一遍。
参考:http://blog.csdn.net/iaccepted/article/details/39273311
可能坑点:
(1)使用了map索引
(2)使用了cin和cout输入输出
(3)没有考虑对名字的编码或者编码错误会导致不同的名字编码后冲突
(4)对学生的所选课课程开辟了过大数组[2501]
#include <iostream>
#include <vector>
#include <map>
#include <string.h>
#include <algorithm>
#include <stdio.h>
using namespace std;
const int num=26*26*26*10+1;
vector<int>stu[num];
int hashName(string name)
{
int len=name.length();
int res=0;
for(int i=0;i<len-1;i++)
{
res=res*26+name[i]-'A';
}
res=res*10+name[3]-'0';
return res;
}
int main()
{
int N,K,index,num,hname,i=0;
char name[5];
scanf("%d %d",&N,&K);
while(i<K)
{
scanf("%d %d",&index,&num);
int j=0;
while(j<num)
{
scanf("%s",name);
hname=hashName(name);
stu[hname].push_back(index);
j++;
}
i++;
}
for(int i=0;i<N;i++)
{
scanf("%s",name);
hname=hashName(name);
printf("%s %d",name,stu[hname].size());
sort(stu[hname].begin(),stu[hname].end());
vector<int>::iterator itb=stu[hname].begin();
vector<int>::iterator ite=stu[hname].end();
for(;itb!=ite;itb++)printf(" %d",*itb);
printf("\n");
}
return 0;
}