从一道简单的题看算法优化 ZOJ PAT Course List for Student

针对PAT练习题CourseList for Student,提出一种优化解决方案,利用姓名和课程ID压缩及排序技巧,显著降低时间复杂度。

一位好友保研后突然想做做ZOJ的PAT Practise,据说里面的题都比较水,但是还是不幸的卡在了Course List for Student (25)上面。

这是PAT的链接:http://pat.zju.edu.cn/contests/pat-practise

【题目大意】

浙大有40000名同学和2500个课程。给出每门课的选课名单,现在有N个同学要查询他们的选课列表,请输出每个人的选课结果。

先给出n(n <= 40000 )和m(m<=2500),n表示要查询选课名单的人数,m代表总共的课程数。

接着给出m个课程的选课名单,格式如下:

x(x<=2500)代表该课程的编号,y(y<=200)代表选该课程的人数,接着输出这y个人的姓名。

最后给出n个学生的姓名,输出每个姓名的选课名单,按课程编号从小到大输出。

姓名由4个字符组成,前3个为大写字母,第四个为一个数字。

内存限制【32M】,时间限制【200ms】

【分析】

如果不考虑数据量,建一个<姓名,课程>的二维表来查询,对于每个查询,只需要输出其后的课程编号即可,显然这里用二维矩阵远远超过内存限制(32M)。同时,最多只有2500*200=500,000个选课数据,总大小40000*2500=100,000,000,有效利用率仅0.5%。

于是可以采用map<string,vector<int> >的格式进行储存,键对应姓名,vector对应该姓名的选课编号。输入完毕后对每个姓名后的选课编号进行排序,然后依照查询输出。

这种时间算法复杂度太高。排序时间为为O(nmlog(m)),每次查询时间O(logn*m),总查询O(nmlog(n))。

查询的效率低下,问题出在对每个姓名都需要进行一次排序,或者提前将所有人员的选课名单都进行排序,显然这两种方法时间复杂度太高。

如果采用计数排序,总时间复杂度略微减少,不过依然是TLE。

进一步分析:

考虑将姓名和课程组成某种数据结构,只进行一次排序即可通过某种方式找出。

由于是对某人选课的课程序号排序,且课程序号至多为2500,于是将<姓名编号,课程编号>压缩成一个id,

id = 姓名编号*2500 + 课程编号。

显然,如此排序后,某人的选课课程必然在[2500*该生姓名编号+1,2500*该生姓名编号+2500]之间。于是对所有id排序一次,然后二分出边界,边界范围内的值即为该生的选课课程编号。

时间复杂度O(plog(p)+nlog(p)) (p<=500,000,n<=40000)

【总结】

进过简单的压缩可以将排序次数从n次减少为1次,说明数据的组织方式即为重要!

【代码】

#include <stdio.h>
#include <map>
#include <set>
#include <string.h>
#include <string>
#include <algorithm>
#include <vector>

using namespace std;

bool get(int& a)
{
	if( scanf("%d",&a) == EOF ) return false ;
	return true ;
}

const int maxn = 2500*200 ;
int nimei[maxn] , cnt ;
int id[26][26][26][10] ;

int bs(int key,int l,int r)
{
	int mid ;
	while ( l <= r )
	{
		mid = ( l + r ) >> 1 ;
		if( nimei[mid] > key )
			r = mid - 1 ;
		else 
			l = mid + 1 ;
	}
	return r ;
}

int main()
{
	int n , m ;
	get(n); get(m);
	int i , j , k ;
	int idCnt = 1 ;
	cnt = 0 ;
	for ( i = 0 ; i < m ; i++)
	{
		get(j); get(k);
		char str[16] ;
		while(k--)
		{
			scanf("%s",str);
			int &temp = id[str[0]-'A'][str[1]-'A'][str[2]-'A'][str[3]^48] ;
			if( temp == 0 )
				temp = idCnt++;
			nimei[cnt++] = ( temp << 12 ) | j ;
		}
	}
	sort(nimei,nimei+cnt);
	char name[16] ;
	while(n--)
	{
		scanf("%s",name);
		int t = id[name[0]-'A'][name[1]-'A'][name[2]-'A'][name[3]^48] ;
		if( t == 0 )
		{
			printf("%s 0\n",name);
			continue;
		}
		j = bs(t<<12,0,cnt-1) ;
		k = bs((t+1)<<12,j,cnt-1);
		printf("%s %d",name,k-j);
		for ( j++; j <= k ; j++)
		{
			printf(" %d",nimei[j] &0x00000fff);
		}
		puts("");
	}
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值