【题解】一本通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\n",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]也可以达到一样的效果,但是提交后是比前者要慢一丢丢(不知道为啥)