【题目描述】
老师在开学第一天就把所有作业都布置了,每个作业如果在规定的时间内交上来的话才有学分。每个作业的截止日期和学分可能是不同的。例如如果一个作业学分为 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;
}
大概就是这样,真的是和智力大冲浪一模一样的思路。