week4作业

题目一 ddl的恐惧

题目描述

ZJM 有 n 个作业,每个作业都有自己的 DDL,如果 ZJM 没有在 DDL 前做完这个作业,那么老师会扣掉这个作业的全部平时分。

所以 ZJM 想知道如何安排做作业的顺序,才能尽可能少扣一点分。

请你帮帮他吧!

input

输入包含T个测试用例。输入的第一行是单个整数T,为测试用例的数量。

每个测试用例以一个正整数N开头(1<=N<=1000),表示作业的数量。

然后两行。第一行包含N个整数,表示DDL,下一行包含N个整数,表示扣的分。

output

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

example

input 1

3
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

output 1

0
3
5

做法与思路

先确定贪心策略。要保证扣分最少,即得分最多。
只要求我们尽可能的完成分值高的任务,但要意识到分值高的任务并不是完成的越早就越好。考虑如下情景,A任务ddl为20日,分值10分,B任务ddl为30日,分值为100分,现在是第20日,我们还是要选择A任务,因为过了ddl后完成任务没有意义,而留给B的时间还有很多。
所以我们考虑策略的时候不能单纯考虑分值,还要综合考虑ddl,要保证选择了该任务之后对之后的任务产生的影响尽可能的小,即该任务选择后不会使其他本可以完成无法完成。依旧是上一个例子,选择A任务后还剩余10天,不会给B任务带来太大的影响。
而如何保证影响小呢,显然是完成的越靠后对之后的影响越小,如果某个任务放在第一天,那么接下来的所有任务都要延后一天。从这个角度考虑,把一个任务放在它ddl的当天对之后的影响最小。
这样我们的贪心策略就差不多形成了,首先要保证分高者得,然后保证选择该任务后对其他任务的影响尽可能小,即把该任务完成时间尽可能的向它的ddl靠拢。
秉持着这样的策略,我们可以按天数倒序选择任务,这样能保证产生的影响最小;先选择ddl最晚的这一天,将ddl在这一天的所有任务加入一个容器,从中选取分值最高的放在这一天,然后以此类推一直到第一天。
这个过程中我们要不断向容器内塞任务的分数,并且要取出最大值,显然大根堆是最适合的数据结构。可以使时间复杂度为nlogn

代码

#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
struct task{
	int p;//扣分
	int ddl;
	task()
	{
		p=0;ddl=0;
	} 
	task(int a,int b)
	{
		p=a;ddl=b;
	}
};
bool bijiao(task a,task b)
{
	return a.ddl<b.ddl;
}
task z[2000];
int sum;
int main(int argc, char** argv) {
	int numble;
	cin>>numble;
	for(int i=0;i<numble;i++)
	{
		priority_queue<int> tem;
		sum=0;
		int shu;
		cin>>shu;
		for(int j=1;j<=shu;j++)
		{
			cin>>z[j].ddl;
		}
		for(int j=1;j<=shu;j++)
		{
			cin>>z[j].p;
			sum+=z[j].p;
		}
		z[0]=task(-1,-1);
		sort(z,z+shu+1,bijiao);
		//cout<<z[3].p<<endl;
		int s=shu;//记录现在走到哪一个任务 
		for(int j=z[shu].ddl;j>=1;j--)
		{
			while(z[shu].ddl==j)
			{
				tem.push(z[shu].p);
				shu--;
			}
			if(!tem.empty())
			{
				//cout<<"top为"<<tem.top()<<endl;
				sum-=tem.top();
				tem.pop();
			}
		}	
		cout<<sum<<endl;
	}
	return 0;
}

题目二 四个数列

题目描述

ZJM 有四个数列 A,B,C,D,每个数列都有 n 个数字。ZJM 从每个数列中各取出一个数,他想知道有多少种方案使得 4 个数的和为 0。

当一个数列中有多个相同的数字的时候,把它们当做不同的数对待。

请你帮帮他吧!

input

第一行:n(代表数列中数字的个数) (1≤n≤4000)

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

output

输出不同组合的个数。

example

input 1

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

output 1

5

做法与思路

这个题一上来的想法肯定是穷举,复杂度为n的四次方,无法通过,但是可以用来验证自己其他方法的正确性。
考虑其他做法,可以将所选的四个数进行二分,a+b+c+d=0即a+b=-c-d,分为a+b与-c-d,先穷举a+b,把结果存入容器T中。然后穷举-c-d,检查其结果在T中出现过几次,最后累加即可。
值得一提的是检查-c-d这个过程,一开始我想用unordered_map,但是不支持,作罢。然后换成了map,结果超时,这使我认为logn复杂度的查询方式是无法通过该题的,然后想了很久如何解决,还是没想到有效的更低复杂度的办法。最后死马当活马医自己写了二分查找,然后就过了…以后能不借助stl就不借助了。

代码

#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
/* run this program using the console pauser or add your own getch, system("pause") or input loop */

int judge[4000*4000];
int main(int argc, char** argv) {
	int a[4000],b[4000],c[4000],d[4000];
//	cout<<pow(2,28);
	int sum=0;
	int numble;
	cin>>numble;
	for(int i=0;i<numble;i++) 
	{
		cin>>a[i];
		cin>>b[i];
		cin>>c[i];
		cin>>d[i];
	}
	for(int i=0;i<numble;i++)
	{
		for(int j=0;j<numble;j++)
		{
			judge[i*numble+j]=a[i]+b[j];
		}
	}
	sort(judge,judge+numble*numble);
	for(int i=0;i<numble;i++)
	{
		for(int j=0;j<numble;j++)
		{
			int s=-c[i]-d[j];
			int l=0,r=numble*numble-1,middle=(l+r)/2;
			while(l<=r)
			{
				middle=(l+r)/2;
				if(judge[middle]==s)
					break;
				else
				{
					if(judge[middle]>s)
						r=middle-1;
					else l=middle+1;	
				}
			}
			if(judge[middle]==s)
			{
				int r=middle,l=middle;
				while((judge[r]==s)&&(r<numble*numble)) r++;
				while((judge[l]==s)&&(l>=0)) l--;
				sum+=r-l-1;
			}
		}
	}
	cout<<sum;
	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 非常想得到那只可爱的猫咪,你能帮帮他吗?

input

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

output

输出新数组 ans 的中位数

example

input 1

4
1 3 2 4
3
1 10 2

output 1

1
8

做法与思路

最直接的办法自然是枚举,复杂度为n的平方,但是n高达1e5,所以放弃该做法。我们可以换个角度“验证”某数是不是中位数。
中位数的位置在数组的中间,我们可以进行二分查找,如果它的位置恰好是中位数的位置则满足条件。
这里首先要去掉绝对值号,先对数组进行排序,排序之后的a[n-1]-a[0]即为中位数查找的右侧边界。对于每个中位数,查询满足a[j]-a[i]<=p的个数,即查找在差数组中p的位置,这里可以对式子进行变形,将其转变为a[j]<=p+a[i],寻找有多少组ij。即查询该数字在数组中的位置,如果小于中位数位置,则在左侧查找;否则在右侧查找,注意这里不能等于就直接输出,要保证p必须是能够在数组中取到的数,所以要筛查到左右边界重合位置。
最后还有一点需要注意,代码中的sum+=sum2-i;在穷举i查找j的过程中,要用sum2-i,因为形成的每一次的i和j是生成了a[i+1]-a[i],a[i+2]-a[i]一直到a[sum2]-a[i],一开始我忽略了这一点调错了好久。

代码

#include <iostream>
#include <algorithm>
using namespace std;
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
int a[100001];	
int n,ans;
int main(int argc, char** argv) {
	while(scanf("%d",&n)!=EOF)
	{
		for(int i=0;i<n;i++)	scanf("%d",&a[i]);
		//cout<<1<<endl; 
		sort(a,a+n);
		int mid_index=(n*(n-1)/2+1)/2;
//		cout<<"中位数位置"<<mid_index<<endl; 
		int pr=a[n-1]-a[0],pl=0;
		int p=(pr+pl)/2;//记录中位数的搜索边界 
		//cout<<1;
		while(pl<=pr)
		{			
			//cout<<1<<endl;	
			int sum=0;
			p=(pr+pl)/2;
			for(int i=0;i<n-1;i++)//统计有几个小于p的
			{			
				//cout<<1<<endl;
				int r=n-1,l=0,m=(r+l)/2;//记录j的搜索边界
				int sum2=-1;//有几个j满足条件 
				 while(l<=r)
				 {
				 	m=(r+l)/2;
				 	if(a[m]<=a[i]+p)
				 	{
				 		sum2=m;
				 		l=m+1;
				 	}
				 	else if(a[m]>a[i]+p)
				 	{
				 		r=m-1;
				 	}
				 }
			//	 if(sum2!=-1)
				 sum+=sum2-i;
			}
		//	cout<<"现在的sum是"<<sum<<endl; 
			if(sum<mid_index)
			{
				pl=p+1;
			}
			else if(sum>=mid_index)
			{
				ans=p;
				pr=p-1;
			}
	//		cout<<"左"<<pl<<"右"<<pr<<endl; 
		}
		printf("%d\n",ans);
	}
	return 0;
	
	
	
	
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值