#10008. 「一本通 1.1 练习 4」家庭作业

【题目描述】

老师在开学第一天就把所有作业都布置了,每个作业如果在规定的时间内交上来的话才有学分。每个作业的截止日期和学分可能是不同的。例如如果一个作业学分为 10,要求在 6 天内交,那么要想拿到这 10 学分,就必须在第 6 天结束前交。

每个作业的完成时间都是只有一天。例如,假设有 7 次作业的学分和完成时间如下:

作业号期限学分
111166
221177
333322
443311
552244
662255
776611

最多可以获得 15 学分,其中一个完成作业的次序为 2,6,3,1,7,5,4注意可能还有其他方法。

你的任务就是找到一个完成作业的顺序获得最大学分。

【输入格式】

第一行一个整数 N,表示作业的数量;

接下来 N 行,每行包括两个整数,第一个整数表示作业的完成期限,第二个数表示该作业的学分。

【输出格式】

输出一个整数表示可以获得的最大学分。保证答案不超过 C/C++ 的 int 范围。

【样例输入】

7
1 6
1 7
3 2
3 1
2 4
2 5
6 1

【样例输出】

15

【数据范围与提示】

对于 20\%20% 的数据,N≤10^3;

对于 40\%40% 的数据,N≤10^4;

对于 60\%60% 的数据,N  N≤10^5;

对于 100\%100% 的数据,N≤10^6,作业的完成期限均小于  7×10^5。

思路:看完代码和我的解析之后你会发现特别像loj之前做过的一道贪心题智力大冲浪简直就是一模一样的思路,其实就是一样的。定义一个bool数组v判断有没有获得当前期限的学分,没有获得过就可以获得,然后再定义一个bk=false,判断到最后到底可不可以获得学分,还定义了一个东西就是省略时间的优化的一个界限(这个是和智力大冲浪唯一的区别难点)

【智力大冲浪的代码】

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int read()//日常快读 
{
	char c=getchar();
	int x=0,f=1;
	while(c<48 || c>57)
	{
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>=48 && c<=57)
	{
		x=x*10+c-48;
		c=getchar();
	}
	return x*f;
}
struct node 
{
	int t,w;//t表示时间期限 //w表示罚款 
}a[110000];
bool cmp(node n1,node n2)
{
	return n1.w>n2.w;//按罚款额从大到小排序 
}
bool bk=false;//这个是判断要不要罚款,一开始初始化全部都要罚款 
bool v[110000];//这个是用来判断当前这个时间期限的所有期限要不要罚款 
int main()
{
	memset(v,true,sizeof(v));//初始化全部都不用罚款 
	int m,n; m=read(); n=read();
	for(int i=1;i<=n;i++) a[i].t=read();
	for(int i=1;i<=n;i++) a[i].w=read();
	sort(a+1,a+n+1,cmp);//按罚款额的大小进行排序 
	for(int i=1;i<=n;i++)
	{
		bk=false;//最开始要罚款 
		for(int j=a[i].t;j>=1;j--)
		/*
			这个是按时间期限来枚举判断
			因为我们要按时间的完成度来罚款
			所以自然就是以时间来罚款的啦
			还有一个就是如果我要知道这一种情况到底是不是不可以的
			那我就一定要把这个时间的每一个时间点都给计算一遍
			如果每一次都不可以的话,说明这个任务就是不能完成的 
		*/ 
		{
			if(v[j]==true)
			/*
				如果这一步进入不了的话
				那就继续上面的j的循环,而不是退出到i的循环
				这样循环是为了对当前的这一个游戏公平
				说当前的游戏不能做的话,
				一定是因为他的所有期限都完成不了,才可以罚款
				所以当前的这一个不行不代表前面的不行
				比如说:期限为4的话,4我们记录过不行,
				但是3,2,1我们没有记录那就不能说我们当前的这种情况要完成不了,
				就不能说当前的这一个游戏我们要罚款   
			*/ 
			{
				v[j]=false;
				/*
					就把当前这种方案变为false,表示记录过
					下一次在出现的话就要判断为false,也就是不能用 
				*/ 
				bk=true;//然后bk=true,说明不用罚款 
				break;//退出循环,到下面判断到底需不需要罚款 
			}
		}
		if(bk==false) m-=a[i].w;
		/*
			注意这一步不是最后判断的,而是在上面的break之后
			就立刻判断,如果经过了上面那一步不成立的话
			就说明当前的这一种方式不可以走,所以就要罚款 
 		*/ 
	}
	printf("%d\n",m);//剩下的钱就是可以拿到的最多的钱 
	return 0;
}
/*
样例输入
10000
7
4 2 4 3 1 4 6
70 60 50 40 30 20 10
样例输出
9950
样例解释:第5个游戏和第6个游戏不能完成
到第五个游戏的时候,1到4这四个期限在v数组里面都已经是false
所以第五个的1就直接false了,就要罚款30元
然后到第六个游戏的时候,1到4这四个期限还是不行的
所以就直接false掉了,就要罚款20元
第七个游戏就可以,因为期限为6在前面没有出现过
所以就是10000-30-20=9950 
*/ 

【家庭作业的代码一:好理解一点的】

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int read()//日常快读,时间减少400多ms 
{
	char c=getchar();
	int x=0,f=1;
	while(c<48 || c>57)
	{
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>=48 && c<=57)
	{
		x=x*10+c-48;
		c=getchar();
	}
	return x*f;
}
struct node//定义结构体 
{
	int x,y;//x表示作业的完成期限 //y表示作业的学分  
}a[1100000];
bool v[710000];//v是判断当前期限的学分是否获取过 
bool cmp(node n1,node n2)
{
	return n1.y>n2.y;
	//因为要获得最大的学分,所以是按照学分从大到小来排序 
}
int main()
{
	memset(v,false,sizeof(v));
	int n; n=read();
	for(int i=1;i<=n;i++)
	{
		a[i].x=read();
		a[i].y=read();
	}
	sort(a+1,a+n+1,cmp);//结构体的目的在此,一连套排序 
	int ans=0,dislike=0;
	//ans是用来记录最大的学分 
	/*
		dislike是用来记录当前不能够再次完成的期限
		(用dislike是好理解,就是当前遇到dislike这个期限的话
		  这个期限对应的学分是拿不到的)
		举个例子:
		2 5 和 2 4 和 2 3 
		我先在期限为2,学分为5的作业上面完成了
		那么这个时候期限为2的我们已经用过的
		所以在 2 4 的时候,dislike在v数组等于false之后
		就会更新为当前不能再次完成的期限2
		然后到 2 3 的时候,就会直接跳过
		因为再进行判断也无法获得学分
		还有一个要注意的就是
		我们的当前到达的这个dislike的前面比他小的期限都是无法完成的
		因为dislike是完全不能完成,所以因为完全不能完成
		就必须是从1-dislike都不能完成
		那么dislike前面的完成期限都是不能完成的
		 
		所以可以说是一个优化
		当然不加这个dislike也是可以的,只不过时间会超限
		因为如果一大堆数都是同一个完成期限的话
		每一次都要判断,时间自然就会长
		所以为了ac,这个dislike是一定要加的 
	*/ 
	for(int i=1;i<=n;i++)	
	{
		if(a[i].x<=dislike) continue;
		/*
			这个就是我上面说的情况,如果期限小于或者等于dislike
			就自动忽略,循环下一个i 
		*/ 
		bool bk=false;
		/*
			bk是用来判断当前可不可以获得学分
			如果不可以,就要更新dislike 
		*/ 
		for(int j=a[i].x;j>=1;j--)
		/*
			从他的最大期限开始循环是因为
			我们为了不漏掉
			比如说期限为2的我们pass掉的
			但是当前的这一个作业期限为6
			如果我们从1开始的话就会pass掉这一个
			就会浪费掉学分
			因为期限为6的这一个是可以获得学分的 
		*/
		{
			if(v[j]==false)
			/*
				如果v[j]==false
				说明当前这个期限还没有获取过学分
				就意味者当前这个期限可以获取学分 
			*/ 
			{
				v[j]=true;//更新为获取过 
				ans+=a[i].y;//增加学分 
				bk=true;//bk判断为可以获取 
				break;//退出循环,到下一步的判断包括bk 
			}
		}
		if(bk==false) dislike=a[i].x;
		/*
			如果bk==false,说明这一个作业不能获得学分
			就要更新当前的dislike的作业期限 
		*/ 
	}
	printf("%d\n",ans);
	return 0;
}

【家庭作业代码二:高级一点的】

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int mymax(int x,int y){return x>y?x:y;}//这个是max的更快版 
inline int read()//日常快读,时间减少400多ms 
{
	char c=getchar();
	int x=0,f=1;
	while(c<48 || c>57)
	{
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>=48 && c<=57)
	{
		x=x*10+c-48;
		c=getchar();
	}
	return x*f;
}
struct node//结构体排序 
{
	int x,y;
}a[1110000];
int cmp(node n1,node n2)
{
	return n1.y>n2.y;
}
int fa[710000];
int find(int x)//这个如果我没记错的话是并查集里面用到的找父亲
/*
	这个找父亲在这里的作用就是判断当前的学分能不能获取
	其实和我之前打的那个代码是一样的
	就是一个一个循环,递减
	然后再判断是不是每一步都失败 
*/ 
{
	if(fa[x]==x) return x;
	/*
		如果当前的fa数组的期限与当前的期限一样
		就把x返回给fx,说明当前这个a[i].x的这个期限是可以用的
		就可以增加学分 
	*/ 
	else return fa[x]=find(fa[x]);
	/*
		否则的话,就返回fa[x],其实是减了1之后的值
		说实在的这个函数解析起来不容易
		大家还是调试吧,之前的那个代码理解起来会容易一些 
	*/ 
}
int main()
{
	int n; n=read(); 
	int ans=0;
	int maxx=0;
	for(int i=1;i<=n;i++)
	{
		a[i].x=read();
		a[i].y=read();
		maxx=max(maxx,a[i].x);//maxx记录最大期限 
	}
	for(int i=1;i<=maxx;i++) fa[i]=i;//最开始的fa数组与完成期限同步 
	sort(a+1,a+n+1,cmp);//排序 
	for(int i=1;i<=n;i++)
	{
		int fx=find(a[i].x);//fx记录返回回来的值 
		if(fx!=0)
		//如果不等于0,说明当前这个期限还有可以完成的余地 
		{
			ans+=a[i].y;//就增加学分 
			fa[fx]=fx-1;//然后这一步其实就是之前的j-- 
		}
	}
	printf("%d\n",ans);
	return 0;
}

大概就是这样,真的是和智力大冲浪一模一样的思路。

 

 

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值