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

版权声明:有女朋友的老江的博客,转载请告知老江 https://blog.csdn.net/qq_42367531/article/details/84329966

【题目描述】

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

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

作业号 期限 学分
11 11 66
22 11 77
33 33 22
44 33 11
55 22 44
66 22 55
77 66 11

最多可以获得 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;
}

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

 

猜你喜欢

转载自blog.csdn.net/qq_42367531/article/details/84329966
今日推荐