pat1039Course List for Student (25)

56 篇文章 0 订阅

题意分析:

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



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值