程序设计思维与实践 Week4作业

A - DDL 的恐惧

题目:
ZJM 有 n 个作业,每个作业都有自己的 DDL,如果 ZJM 没有在 DDL 前做完这个作业,那么老师会扣掉这个作业的全部平时分。
所以 ZJM 想知道如何安排做作业的顺序,才能尽可能少扣一点分。
请你帮帮他吧!

Input
输入包含T个测试用例。输入的第一行是单个整数T,为测试用例的数量。
每个测试用例以一个正整数N开头(1<=N<=1000),表示作业的数量。
然后两行。第一行包含N个整数,表示DDL,下一行包含N个整数,表示扣的分。

Output
对于每个测试用例,您应该输出最小的总降低分数,每个测试用例一行。

思路:
这题应使用贪心算法。其贪心策略为:从最后一天开始,从所有未到DDL的作业里选择一个分数最高的安排在当天,这样一直到第一天安排完毕。针对该贪心策略有两种复杂度不同的实现方法,这里我采用的是O(nlogn)的方法。方法为:从最后一天开始,逐步向前一天推进。每到新的一天,就将ddl为该天的所有作业放入最大堆中(最大为分数最大),然后取出最大堆的根元素,将其安排在当天。该过程持续到第一天安排完毕后结束。之后堆中所剩作业分数之和即为最小总降低分数。

代码:

#include <iostream>
#include <queue>
using namespace std; 
struct ddl
{
	int time;
	int point;
};
priority_queue<int> pile;
ddl d[1050];
int arrange[2050];
int main()
{
	int n,m;
	cin>>n;
	for(int i=0;i<n;i++)
	{
		cin>>m;
		for(int j=0;j<m;j++)
		{
			cin>>d[j].time;
		}
		int maxtime=0;
		for(int j=0;j<m;j++)
		{
			if(d[j].time>maxtime)
				maxtime=d[j].time;
			cin>>d[j].point;
		}
		for(int j=maxtime;j>=1;j--)
		{
			for(int k=0;k<m;k++)
			{
				if(d[k].time==j)
					pile.push(d[k].point);
			}
			if(pile.empty())
				continue;
			arrange[j]=pile.top();
			pile.pop();
		}
		int scores=0;
		while(!pile.empty())
		{
			scores=scores+pile.top();
			pile.pop();
		}
		cout<<scores<<"\n";
	}
}

B - 四个数列

题目:
ZJM 有四个数列 A,B,C,D,每个数列都有 n 个数字。ZJM 从每个数列中各取出一个数,他想知道有多少种方案使得 4 个数的和为 0。当一个数列中有多个相同的数字的时候,把它们当做不同的数对待。请你帮帮他吧!

Input
第一行:n(代表数列中数字的个数) (1≤n≤4000)
接下来的 n 行中,第 i 行有四个数字,分别表示数列 A,B,C,D 中的第 i 个数字(数字不超过 2 的 28 次方)

Output
输出不同组合的个数。

思路:
该题给人的第一印象便是暴力枚举,但是暴力枚举的复杂度高达n的4次方,显然是无法使用的。再考虑到式子 a+b+c+d=0 可变形为 a+b=-(c+d) 的特殊性。若将两个数组分为一组,进行暴力枚举,再将其中一个合并后的数组升序排列,根据上述式子的特殊性,可以利用二分查找的方法来统计数目。这样一来,复杂度为n的平方乘以logn^2。大大降低了复杂度。

代码:

#include <iostream>
#include <queue>
using namespace std; 
struct ddl
{
	int time;
	int point;
};
priority_queue<int> pile;
ddl d[1050];
int arrange[2050];
int main()
{
	int n,m;
	cin>>n;
	for(int i=0;i<n;i++)
	{
		cin>>m;
		for(int j=0;j<m;j++)
		{
			cin>>d[j].time;
		}
		int maxtime=0;
		for(int j=0;j<m;j++)
		{
			if(d[j].time>maxtime)
				maxtime=d[j].time;
			cin>>d[j].point;
		}
		for(int j=maxtime;j>=1;j--)
		{
			for(int k=0;k<m;k++)
			{
				if(d[k].time==j)
					pile.push(d[k].point);
			}
			if(pile.empty())
				continue;
			arrange[j]=pile.top();
			pile.pop();
		}
		int scores=0;
		while(!pile.empty())
		{
			scores=scores+pile.top();
			pile.pop();
		}
		cout<<scores<<"\n";
	}
}

C - 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 非常想得到那只可爱的猫咪,你能帮帮他吗?

Input
多组输入,每次输入一个 N,表示有 N 个数,之后输入一个长度为 N 的序列 cat, cat[i] <= 1e9 , 3 <= n <= 1e5

Output
输出新数组 ans 的中位数

思路:
该题给人的第一感觉也是枚举,但在这样庞大的数据量下枚举的n的2次方复杂度显然不行。因此, 这里就另辟蹊径 ,用一种验证中位数的方法来求中位数。一般来说 ,中位数不会大于一组数中的(max-min) ,因此,我们就使用二分法对一组0 - (max - min)的数中寻找名次为 (len + 1) / 2 且存在于新数组中的数。于是,新的问题出现了,如何判断一个数p在新数组中的名次?这里我们可以通过统计 ans[ j ]-ans[ i ] <= p 这一式子中满足 条件的i,j个数,即在 ans[i]+p>=ans[j] 这一式子中满足条件的i的个数,但在作此转化前需要将cat数组升序排列以达到去掉绝对值的效果。而对i的个数的统计,同样可以使用二分查找。但是要注意这两个二分查找的一些细节,因为根据你二分查找的写法不一样,结果可能差异很大,需要不断调试。

代码:

#include <stdio.h>
#include <algorithm>
using namespace std;
int flag=0;
int a[100050];
int rank(int p,int size)
{
	int rank=0;
	for(int i=0;i<size-1;i++)
	{
		int left=i+1;int right=size-1;int mid;
		int ans=-1;
		if(p+a[i]<a[left])
			continue;
		while(left<=right)
		{
			mid=(left+right)/2;
			if(a[mid]<=p+a[i])
			{
				ans=mid;
				left=mid+1;
			}
			else
			{
				right=mid-1;
			}
		} 
		if(ans==-1)
			continue;
		rank=rank+ans-i;
	}
	return rank;
}

int main()
{
	int n;
	while(scanf("%d",&n)!=EOF)
	{
		for(int i=0;i<n;i++)
			scanf("%d",&a[i]);
		sort(a,a+n);
		int max=a[n-1]-a[0];
		int left=0;int right=max;int mid;
		int ans;
		while(left<=right)
		{
			mid=(left+right)/2;
			int r=rank(mid,n);
			if(r<(n*(n-1)/2+1)/2)
			{
				left=mid+1;
			}
			else
			{
				ans=mid;
				right=mid-1;
			}
		}
		printf("%d\n",ans);
	}
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值