Week4作业——贪心/二分

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],这样就可以得到满足条件的二元组的个数,再进行求和即可。
错误记录:
  • 一开始计算满足条件的二元组的个数时,直接进行嵌套循环枚举,结果TL。所以之后利用其有序性进行二分处理。
  • 在计算中位数时,刚开始没有考虑到应该是排名大于等于"中位"的第一个数,导致出错:假设中位数为x[i],且有x[i]<m<x[i+1],则对于m同样满足排名等于“中位”。
  • 由于使用cin输入导致TL。之前一直使用cin比较顺手,而且几乎没有出过问题。但是这道题失败了。考虑到其他地方应该没错的前提下,增加std::ios::sync_with_stdio(false)仍然TL,最后改成scanfAC。
实现:
#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),前者是在beginend间返回第一个不小于num的数的位置,后者则是大于。
  • 以后尽量使用scanf()而非cin
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值