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

一位好友保研后突然想做做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("");
	}
}


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
zoj1091是一道经典的动态规划问,它的目描述如下: 有一条直线路,长度为n,起点为0,终点为n。有m个加油站,每个加油站有一个加油量p,和一个位置d,表示在距离起点d的地方可以加p升油。车油箱的容量为c升,每行驶1千米消耗1升油,请问车能否从0到n行驶,如果能,最少需要加几次油? 这道问可以用动态规划的方法求解。我们可以定义一个一维数组dp,其中dp[i]表示车到达距离i的位置时最少需要加几次油。 那么如何进行状态转移呢? 对于每个加油站,我们可以考虑是否在这个加油站加油。如果不加油,那么dp[i]的值就等于dp[i-1],因为车没有加油,可以继续往前开。 如果要在这个加油站加油,那么我们需要找到在之前的哪个位置加油次数最少。假设这个加油站的位置是j,加油量是p,那么我们需要在距离j-p的位置加油,这样才能到达加油站j。那么在距离j-p的位置加油的次数就是dp[j-p],加上这个加油站的加油次数1,就是dp[j-p]+1。我们还需要判断车的油箱容量是否够用,如果不够用,那么就不能在这个加油站加油。我们可以用一个变量tank来记录油箱中的油量,每经过一个位置就减去1,如果遇到加油站就加上p。 最后,dp[n]就是最终的答案,表示车到达终点n时最少需要加几次油。 下面是使用Python实现这道的代码:

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值