week4作业

A - DDL 的恐惧
ZJM 有 n 个作业,每个作业都有自己的 DDL,如果 ZJM 没有在 DDL 前做完这个作业,那么老师会扣掉这个作业的全部平时分。
所以 ZJM 想知道如何安排做作业的顺序,才能尽可能少扣一点分。
请你帮帮他吧!Input输入包含T个测试用例。输入的第一行是单个整数T,为测试用例的数量。
每个测试用例以一个正整数N开头(1<=N<=1000),表示作业的数量。
然后两行。第一行包含N个整数,表示DDL,下一行包含N个整数,表示扣的分。Output对于每个测试用例,您应该输出最小的总降低分数,每个测试用例一行。
Sample Input3
3
3 3 3
10 5 1
3
1 3 1
6 2 3
7
1 4 6 4 2 4 3
3 2 1 7 6 5 4
Sample Output
0
3
5
Hint:
上方有三组样例。
对于第一组样例,有三个作业它们的DDL均为第三天,ZJM每天做一个正好在DDL前全部做完,所以没有扣分,输出0。
对于第二组样例,有三个作业,它们的DDL分别为第一天,第三天、第一天。ZJM在第一天做了第一个作业,第二天做了第二个作业,共扣了3分,输出3。
题目总结:
对于这道题是要求扣最少的分,也就是得最多的分。类似于背包问题,我们不如将它称为若干离散性任务价值最大化问题。我们从问题身上可以总结出五种特性;1,任务起始时间;2,任务截止时间;3,任务用时;4,任务价值;5,规定的总用时;
对于这道题我们可以简单的与背包问题做一下对比,该问题中所有任务起始时间一样,截止时间不同,用时一样,价值不同,总时间已知; 而对于背包问题所有任务起始时间一样,截止时间一样,用时不一样,价值不同,总时间已知;在这五个因素中,单个任务的用时与价值决定了其优秀性,显然背包问题不能用贪心解决的原因是各任务用时与价值都不确定,这也就没有绝对的理由确定那个任务是最优秀的那个;而对于本问题中,每个任务的用时一样,则有绝对的理由证明价值越高的任务就越优秀。因此,确定利用贪心思想便可以解决本题。当然,优先选择优秀的任务,只是贪心的第一步,贪心思想的另一个要求是对接先来进行的选择产生的不利影响也最小。由题意可知,一个任务可以安排的时间范围是确定的,则具体安排到那个时间是一个重要的选择问题。在这里我们可以将每一个任务看作是从0到其DDL的一条线段,如下面一样,我们从第一条直线上选择一个点做一条垂线,显然该点越往后其影响的任务数量就越少,如果其选择最左端,那么显然会影响到所有任务。
————————————
————
————————
代码中,我们以时间为参考标准从后往前遍历,其中借助了堆的性质,为每一个时间安排满足该时间的可选择的最优秀的任务。

#include<iostream>
#include<algorithm>
#include<string.h>
#include<queue>
using namespace std;
int ans=0;
struct node {
	int value,del;
	node() {
	}
	node(int a,int b) {
		value=a;
		del=b;
	}
	bool operator<(const node& p)const {
		return value==p.value? del>p.del:value<p.value;
	}
};
priority_queue<node> ni;
bool cmp(const node& a,const node& b) {
	return a.del==b.del? a.value>b.value:a.del>b.del;
}
int main() {
	int T;
	cin>>T;
	for(int i=0; i<T; i++) {
		int N;
		cin>>N;
		node* no=new node[N];
		for(int i=0; i<N; i++) {
			cin>>no[i].del;
		}
		for(int i=0; i<N; i++) {
			cin>>no[i].value;
		}
		sort(no,no+N,cmp);
		int maxdel=no[0].del;
		int j=0;
		for(int i=maxdel; i>0; i--) {
			while(j<N&&no[j].del==i) {
				ni.push(no[j++]);
			}
			if(!ni.empty())
				ni.pop();
		}
		int ans=0;
		while(!ni.empty()) {
			ans=ans+ni.top().value;
			ni.pop();
		}
		cout<<ans<<endl;
		delete[] no;
	}
}在这里插入代码片

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输出不同组合的个数。
Sample Input
6
-45 22 42 -16
-41 -27 56 30
-36 53 -37 77
-36 30 -75 -46
26 -38 -10 62
-32 -54 -6 45
Sample Output
5
Hint:
样例解释: (-45, -27, 42, 30), (26, 30, -10, -46), (-32, 22, 56, -46),(-32, 30, -75, 77), (-32, -54, 56, 30).
题目总结:
对于本道题,主要是对于二分思想的应用,显然根据题目所给出的数据范围可知,暴力求解肯定会超时,二分思想是建立在有序数组上的,但对于数组排序花费的时间相对于暴力求解来说比较合算。这样,我们将A,B,C,D分成两组,利用二重循环,将A,B中任意两个元素相加的值算出来保留在一个vector(其实感觉利用数组会更好)中,然后将其从大到小排序。然后利用二重循环,对C,D进行任意
两元素和的遍历,然后利用二分思想求出每次获取值在vector中出现的第一个位置与最后一个位置,利用二者即可将每一遍历到的值所对应的选法种数获取到,由此便可在规定的时间内求出答案;

#include<iostream>
#include<vector>
#include<string.h>
#include<algorithm>
using namespace std;
int a[4000];
int b[4000];
int c[4000];
int d[4000];
int ans=0;
int func(int a,vector<int>& b)
{
	int right=b.size()-1;
	int left=0;
	while(right!=left)
	{
		int mid=(left+right)/2;
		if(b[mid]>=a)
		{
			right=mid;
		}
		else
		{
			left=mid+1;
		}
	}
	if(b[left]==a)
	return left;
	else 
	return -1;
}
int func2(int a,vector<int>& b)
{
	int right=b.size()-1;
	int left=0;
	while(right-left>1)
	{
		int mid=(left+right)/2;
		if(b[mid]>a)
		{
			right=mid-1;
		}
		else
		{
			left=mid;
		}
	}
	if(b[right]==a)
	return right;
	if(b[left]==a)
	return left;
	else 
	return -1;
 } 
int main()
{
	memset(a,0,sizeof(a));
	memset(b,0,sizeof(b));
	memset(c,0,sizeof(c));
	memset(d,0,sizeof(d));
	int n;
	cin>>n;
	vector<int> fun;
	for(int i=0;i<n;i++)
	{
		cin>>a[i]>>b[i]>>c[i]>>d[i];
	}
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n;j++)
		{
			fun.push_back(a[i]+b[j]);
		}
	}
	sort(fun.begin(),fun.end());
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n;j++)
		{
			int ee=c[i]+d[j];
			int begin=func(-ee,fun);
			int end=func2(-ee,fun);
			if(begin!=-1)
			ans=ans+end-begin+1;
		}
	}
	cout<<ans<<endl;
}在这里插入代码片

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 的中位数Sample Input
4
1 3 2 4
3
1 10 2
Sample Output
1
8
题目总结:
考虑到这道题目的数据范围便知暴力求解要超时,而其实这道题是对于二分答案思想的一种应用,感觉这种解法比较优秀,有一种四两拨千斤的感觉,比较灵活,当然,也比较难以想到。从这道题出发,我们知道的便是这个中位数的范围,而对于中位数有着其本身的性质,其本身的位置是明确的,而其前面的数字都是小于等于它的。有答案的范围,有排除条件,这也就构成了应用二分思想的基本元素。由此,我们便可以利用两边逼近的原则,对答案进行逼近;在这里,我们首先对给出数组进行从小到大的排序,在对答案进行二分的内部,我们对每一个点i进行考虑,找出max(j)使得j-1>i且a[j-1]-a[i]<=midans;则j-i-1的结果就是对于当前i来讲所能提供的小于等于midans的个数;在这里,我们可以在对每个i进行个数求解时再次利用二分思想,这时时间复杂度为nlogn*logn,但是对于数组来讲,我们已经使其变成有序的了,且是从小到大的,这样,我们可以利用模拟双指针的方法对每个i进行个数求解,这样时间复杂度就降成nlogn了。
二分思想核心:找到待求解的一种特性与范围,利用该特性对范围进行减半缩小,直至最后两端逼近为一个数,即为答案。

#include<iostream>
#include<algorithm>
#include<string.h>
#include<stdio.h>
using namespace std;
int fun[100000];
int N;
void func(int* fun) {
	int beginans=0;
	int lastans=fun[N-1]-fun[0];
	int midans=(beginans+lastans)/2;
	int stage=(1+((N-1)*N)/2)/2;
	int pos=0;
	int ans=-1;
	while(beginans<=lastans) {
		pos=0;
		midans=(beginans+lastans)/2;
		int cc=1; 
		for(int i=0; i<N; i++) {
			while(cc<N&&fun[cc]<=fun[i]+midans)
			{
				cc=cc+1; 
			}
			pos=pos+cc-i-1;
		}
		if(pos<stage) {
			beginans=midans+1;
		} else {
			ans=midans;
			lastans=midans-1;
		}
	}
	cout<<ans<<endl;
}
int main() {
	while(scanf("%d",&N)!=EOF) {
		for(int i=0; i<N; i++) {
			scanf("%d",&fun[i]);
		}
		sort(fun,fun+N);
		func(fun);
	}
}在这里插入代码片
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值