【题解】一本通1430:家庭作业

题目

1430:家庭作业

时间限制: 1000 ms 内存限制: 65536 KB
提交数: 1988 通过数: 467

【题目描述】

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

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

作业号 1 2 3 4 5 6 7
期限 1 1 3 3 2 2 6
学分 6 7 2 1 4 5 1

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

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

【输入】

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

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

【输出】

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

【输入样例】

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

【输出样例】

15

分析

乍一看,这题和一本通的智力大冲浪简直是一模一样!
智力大冲浪代码

#include <cmath>
#include <cstdio>
#include <string>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#define N 510
#define F(i,a,b) for(int i=a;i<=b;i++)
#define UF(i,a,b) for(int i=a;i>=b;i--)

using namespace std;

bool visit[N];
int n,m,ot;

struct game{
	int e, ot;
}G[N];

bool cmp(game a, game b){
	return a.ot > b.ot;
}

int main()
{
	scanf("%d%d",&m,&n);
	F(i,1,n){
		scanf("%d",&G[i].e);
	}
	F(i,1,n){
		scanf("%d",&G[i].ot);
	}
	sort(G+1,G+1+n,cmp);
	/*F(i,1,n){
		cout << G[i].e << " " << G[i].ot << endl; 
	}*/
	F(i,1,n){
		int t = 0;
		UF(j,G[i].e,1){
			if(!visit[j]){

				visit[j]++;
				t = 1;
				break;
			}
		}
		if(!t){
			ot += G[i].ot;
		}

	}
	m-=ot;
	cout << m << endl;
	return 0;
}

但是,按照原写法,这题是会超时的,就算加快读,也要TLE两个点
所以必须要加两个优化

1

发现,如果当前物品已经无处可放,也就是说可放的日期已经放满,那我们的第二层循环就是完全无用且费时的
所以,我们可以一个变量q来记录前几个日期已被放满,如果G[i]<=q,那么当前物品就无处可放了
发现有物品无处可放时,也就是进行了一次无用循环后,q更新

2

然而只有上面的优化是无法解决问题的
从别人那知道,这是可以用并查集优化的
用并查集代替第二层循环
先copy一下别人的code


#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
",ans);
	return 0;
————————————————
版权声明:本文为CSDN博主「TJ.」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_42367531/article/details/84329966
}

发现用fa[]数组来存储对于第G[i].e天,离G[i].e最近的并且没有被使用过的是哪一天,在不断的更新(fa[x]=find(fa[x])中,find()可以达到常数查询
解释一下
fa[fx]=fx-1;//然后这一步其实就是之前的j--
就是fx这一天用掉之后,fa[fx]肯定要小于fx了,就减了个一,其实fa[fx]=fa[fx-1]也可以达到一样的效果,但是提交后是比前者要慢一丢丢(不知道为啥)

并查集优化,确实很巧妙
原文地址:https://www.cnblogs.com/ZhengkunJia/p/12264637.html