Week4作业——贪心/二分
贪心—DDL的恐惧
ZJM 有 n 个作业,每个作业都有自己的 DDL,如果 ZJM 没有在 DDL 前做完这个作业,那么老师会扣掉这个作业的全部平时分。所以 ZJM 想知道如何安排做作业的顺序,才能尽可能少扣一点分。请你帮帮他吧!
输入包含T个测试用例。输入的第一行是单个整数T,为测试用例的数量。每个测试用例以一个正整数N开头(1<=N<=1000),表示作业的数量。然后两行。第一行包含N个整数,表示DDL,下一行包含N个整数,表示扣的分。
对于每个测试用例,您应该输出最小的总降低分数,每个测试用例一行。
思路:
- 考虑到应该减少扣分,所以将扣分值从大到小排列,遍历:对于每个作业,寻找ddl之前的空闲时间,如果存在那么可以完成,否则直接扣分——和课上例题几乎一样。
实现:
#include<iostream>
#include<algorithm>
#define _for(i,a,b)for(int i=(a);i<(b);++i)
#define _rep(i,a,b)for(int i=(a);i<=(b);++i)
using namespace std;
struct homework
{
int ddl,score;
homework(){};
bool operator<(homework & x)const
{return score > x.score;};
};
homework work[1010];
int N,T,sum=0;
bool time[1010]={0};
bool flag;
int main()
{
cin>>T;
while(T>0)
{
cin>>N;
_for(i,0,N)cin>>work[i].ddl;
_for(i,0,N)
{cin>>work[i].score;time[i+1]=0;}
sort(work,work+N);
sum = 0;
_for(i,0,N)
{
flag = false;
for(int j=work[i].ddl;j>0;--j)
{
if(time[j]==0)
{
time[j]=1;
flag = true;
break;
}
}
if(flag==false)sum+=work[i].score;
}
cout<<sum<<endl;
--T;
}
return 0;
}
反思:
- 注意记录空闲时间的数组,赋值时应当是从1开始的N个数而非从0开始,刚开始疏忽了这点导致WA。
二分—四个数列
ZJM 有四个数列 A,B,C,D,每个数列都有 n 个数字。ZJM 从每个数列中各取出一个数,他想知道有多少种方案使得 4 个数的和为 0。当一个数列中有多个相同的数字的时候,把它们当做不同的数对待。请你帮帮他吧!
第一行:n(代表数列中数字的个数) **(1≤n≤4000)**接下来的 n 行中,第 i 行有四个数字,分别表示数列 A,B,C,D 中的第 i 个数字(数字不超过 2 的 28 次方)
输出不同组合的个数。
思路:
- 枚举AB数列各种和,放入数组
tmp
当中; - 枚举CD数列各和,每得到一个和搜索其相反数出现在
tmp
中的位置——第一个和最后一个,这样可以比较高效地得到相反数的个数——即对应解的个数。注意两者区别:前者在满足条件之后缩小右边界,后者则是扩大左边界。
实现:
#include<iostream>
#include<algorithm>
#define _for(i,a,b)for(int i=(a);i<=(b);++i)
using namespace std;
int tmp[16000010];
int a[4010],b[4010],c[4010],d[4010];
int n,index,num0=0;
int find_first(int key,int num)
{//在tmp数组中从1到num寻找key
int l=1,r=num,mid,ans=-1;
while(l<=r)
{
mid = (l+r)>>1;
if(tmp[mid]==key)
{
ans=mid;
r=mid-1;
}
else if(tmp[mid]>key)
r=mid-1;
else l = mid+1;
}
return ans;
}
int find_last(int key,int num)
{
int l=1,r=num,mid,ans=-1;
while(l<=r)
{
mid = (l+r)>>1;
if(tmp[mid]==key)
{
ans=mid;
l=mid+1;
}
else if(tmp[mid]<key) l=mid+1;
else r=mid-1;
}
return ans;
}
int main()
{
cin>>n;
_for(i,1,n)cin>>a[i]>>b[i]>>c[i]>>d[i];
_for(i,1,n)
{
_for(j,1,n)
{
index = (i-1)*n+j;
tmp[index]=a[i]+b[j];
}
}
sort(tmp+1,tmp+1+n*n);
int add,count=0;
_for(i,1,n)
{
_for(j,1,n)
{
add=-(c[i]+d[j]);
int first=find_first(add,n*n);
int last=find_last(add,n*n);
if(first!=-1)
count+=(last-first+1);
}
}
cout<<count<<endl;
return 0;
}
二分—TT 的神秘礼物
TT 是一位重度爱猫人士,每日沉溺于 B 站上的猫咪频道。有一天,TT 的好友 ZJM 决定交给 TT 一个难题,如果 TT 能够解决这个难题,ZJM 就会买一只可爱猫咪送给 TT。任务内容是,给定一个 N 个数的数组 cat[i],并用这个数组生成一个新数组 ans[i]。新数组定义为对于任意的 i, j 且 i != j,均有 ans[] = abs(cat[i] - cat[j]),1 <= i < j <= N。试求出这个新数组的中位数,中位数即为排序之后 (len+1)/2 位置对应的数字,’/’ 为下取整。TT 非常想得到那只可爱的猫咪,你能帮帮他吗?
多组输入,每次输入一个 N,表示有 N 个数,之后输入一个长度为 N 的序列 cat, cat[i] <= 1e9 , 3 <= n <= 1e5
输出新数组 ans 的中位数
思路:
- 采用课上讲解的方法:
- 考虑到数列中全部数不小于0,因此中位数必定是在0到最大值之间——在将数组排序后,最大值即为
x[n]-x[1]
。因此对此区间进行二分,最终得到第一个排名大于等于中间排名的数——即为中位数。 - 因此需要计算一个数的排名。在有序的前提下,即
xj - xi <= x(j>i)
。可以直接枚举每一个i
,考虑到单调递增,因此再次对于x[j]
进行二分处理,得到最后一个满足条件的x[j]
,这样就可以得到满足条件的二元组的个数,再进行求和即可。
- 考虑到数列中全部数不小于0,因此中位数必定是在0到最大值之间——在将数组排序后,最大值即为
错误记录:
- 一开始计算满足条件的二元组的个数时,直接进行嵌套循环枚举,结果
TL
。所以之后利用其有序性进行二分处理。 - 在计算中位数时,刚开始没有考虑到应该是排名大于等于"中位"的第一个数,导致出错:假设中位数为
x[i]
,且有x[i]<m<x[i+1]
,则对于m同样满足排名等于“中位”。 - 由于使用
cin
输入导致TL
。之前一直使用cin
比较顺手,而且几乎没有出过问题。但是这道题失败了。考虑到其他地方应该没错的前提下,增加std::ios::sync_with_stdio(false)
仍然TL
,最后改成scanf
AC。
实现:
#include<iostream>
#include<algorithm>
#define _for(i,a,b)for(int i=(a);i<(b);++i)
#define _rep(i,a,b)for(int i=(a);i<=(b);++i)
using namespace std;
int n,mid,p,index,l,r,ans;
int a[100100];
int rank(int key)
{
int count1=0,count2=0;
_for(i,1,n)
{
int l=i+1,r=n,mid;
while(l<=r)
{
mid = (l+r)>>1;
if(a[i]+key>=a[mid])
{
count2=mid;
l=mid+1;
}
else r=mid-1;
}
if(count2!=0)count1+=(count2-i);
}
return count1;
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
index = (n*(n-1)/2+1)>>1;
_rep(i,1,n)scanf("%d",&a[i]);//注意从1开始存储
sort(a+1,a+n+1);
l=0,r=a[n]-a[1];
while(l <= r)
{
mid = (l+r)>>1;
if(rank(mid) >= index)
{
ans = mid;
r = mid-1;
}
else l=mid+1;
}
printf("%d\n",ans);
}
return 0;
}
作业总结:
- 感觉二分查找的具体实现并不困难,但是想到使用二分不容易——至少第三题刚开始没有什么想法,经过讲解才知道如何使用二分,在使用的时候就直接套用了模板。——需要增加练习。
- STL中的二分查找函数:
lower_bound(begin,end,num)
以及upper_bound(begin,end,num)
,前者是在begin
到end
间返回第一个不小于num
的数的位置,后者则是大于。 - 以后尽量使用
scanf()
而非cin
。