第4周作业(整数二分的运用)

A - DDL 的恐惧

问题描述

已知有n项作业的ddl和对应的分值,一天只仅且只能完成一项作业,ddl前未完成的作业扣取作业对应的分值。请合理规划完成各项作业,以使被扣取的分值最少。

Input

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

Output

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

Example

Input

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

0
3
5

解题思路

思路一:
可以使用贪心策略,每一次都选当前分值最大的一项作业完成。因此我们要将给出的n项作业按作业分值进行排序,然后按从大到小的顺序一一进行分配。此外为了确保当前的选择对其他作业的选择影响最小,就需要选择离该项作业的ddl最近的空闲时间完成该项作业。因此我们需要一个数组来记录当前每一天的空闲情况,如果找不到可以放置该作业的空闲时间,那么就说明这项作业完成不了,那么就将该作业对应的分值扣除。

思路二:
也可以按天进行分配,从ddl在这一天及之后的作业里挑选出分值最大的作业,为此我们为了降低复杂度,我们采用堆的方式按作业分值的大小存储作业。所以我们首先要先将给出的n项作业按作业ddl进行排序,从当前最大的ddl处进行分配,初始堆中只有ddl最大的一项作业,然后按ddl从大到小的顺序依次入堆。当相邻入堆的两项作业的ddl不相同时,最大元素出堆。重复该操作,直到所有元素入堆,若还未分配到第一天,则继续出堆,直到第一天为止。

代码1

#include<iostream>
#include<stdio.h>
#include<algorithm>
using namespace std;
	
struct work{
	int ddl;
	int value;
	work(){}
	work(int a1, int b1){
		ddl = a1, value = b1;
	}
	bool operator < (const work&a){
		return value<a.value;
	}
};

work a[1500];

int ddl(int n){
	int d1=0, d2=0, sum=0;
	int b[1500] = {0};
	for(int i=0;i<n;i++)
		cin>>a[i].ddl;
	for(int i=0;i<n;i++)
		cin>>a[i].value;
	sort(a, a+n);
	bool judge;
	for(int i=n-1;i>=0;i--)
	{
		judge=false;
		for(int j=a[i].ddl;j>0;j--)
		{
			if(b[j]==0)
			{
				b[j]=1;
				judge=true;
				break;
			}
		}
		if(!judge)
			sum=sum+a[i].value;
	} 
	return sum;
} 
int main()
{
	int n,m,sum;
	cin>>n;
	for(int i=0;i<n;i++)
	{
		cin>>m;
		sum = ddl(m);
		cout<<sum<<endl;
	}
	return 0;	
}

代码2

#include <iostream>
#include <algorithm>
#include <functional> 
#include <vector>
using namespace std;

struct work{
	int ddl;
	int value;
	work(){}
	work(int a, int b){
		ddl = a, value = b;
	}
	bool operator < (const work&a){
		return value<a.value;
	}
};
bool compare(const work&a,const work&b){
	return a.ddl<b.ddl;
}

work a[1000];

int ddl(int n)
{
	work now;
	int d1=0, d2=0, sum=0;
	for(int i=0;i<n;i++)
		cin>>a[i].ddl;
	for(int i=0;i<n;i++)
		cin>>a[i].value;
	sort(a, a+n, compare);
	vector<work> b;
	b.push_back(a[n-1]);
	int days = a[n-1].ddl;
	make_heap(b.begin(),b.end());
	for(int i=n-2;i>=0;i--)
	{
		d1 = a[i].ddl, d2=a[i+1].ddl;
		if(d2==d1) 
		{
			b.push_back(a[i]);
			push_heap(b.begin(),b.end());
		}
		else
		{
			if(!b.empty() )
			{
				now = b.front();
				pop_heap(b.begin(),b.end());
				b.pop_back();
				days--;
				if(b.empty() )
					days = a[i].ddl; 
				else
				{
					now = b.front();
					if(days > now.ddl)
						days = now.ddl;
				}
				b.push_back(a[i]);
				push_heap(b.begin(),b.end());
			}
		}
	}
	while(days!=0&&!b.empty())
	{
		pop_heap(b.begin(),b.end());
		b.pop_back();
		days--;
		now = b.front();
		if(days > now.ddl)
			days = now.ddl;
	}
	for(int i=0;i<b.size();i++)
		sum = sum + b[i].value;	
	return sum;
}
int main()
{
	int n,m,sum;
	cin>>n;
	for(int i=0;i<n;i++)
	{
		cin>>m;
		sum = ddl(m);
		cout<<sum<<endl;
	}
	return 0;	
}

B - 四个数列

问题描述

有四个数列 A,B,C,D,每个数列都有 n 个数字。现从每个数列中各取出一个数,求共有多少种方案使得 4 个数的和为 0。
当一个数列中有多个相同的数字的时候,把它们当做不同的数对待。

Input

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

Output

输出不同组合的个数。

Example

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

Output

5

解题思路

这是一个枚举问题,但如果直接枚举,算法的复杂度将达到n的4次方级别。因此我们要将数组进行两两分配,分别枚举计算两个数组的和,并将其中一个数组的和求反,然后分别储存两个大数组。之后使用枚举,查找两个元素中的相同元素,又因为当一个数列中有多个相同的数字的时候,把它们当做不同的数对待,因此我们需要查找到一个元素出现的最早和最晚位置以便统计。为了降低复杂度,查找时我们使用二分查找(本题考查的也主要是整数二分查找)。

代码

#include <iostream>
#include <algorithm> 
#include <stdio.h>
#include <vector>
using namespace std;

vector<int> sum1,sum2;

int last1(int x, int n){
	int l = 1, r = n, ans = -1;
	while( l <= r)
	{
		int mid=(l + r )/2;
		if(sum1[mid] == x){
			ans = mid;
			l = mid+1;
		}
		else if( sum1[mid] > x) 
			r = mid - 1 ;
		else	
		    l = mid+1;
	}
	return ans;
} 
int begin1(int x, int n){
	int l = 1, r = n, ans=-1;
	while(l<=r)
	{
		int mid = (l+r)/2;
		if(sum1[mid] == x){
			ans = mid;
			r = mid-1;
		}
		else if( sum1[mid] > x) 
			r = mid - 1 ;
		else	
		    l = mid+1;
	}
	return ans;
} 


int main()
{
	int n;
	cin>>n;
	int *a, *b, *c, *d;
	a = new int[n], b = new int[n], c = new int[n], d = new int[n];
	for(int i=0;i<n;i++)
		cin>>a[i]>>b[i]>>c[i]>>d[i];
	int sum = 0, count = 0;
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++)
		{
			sum = a[i]+b[j];
			sum1.push_back(sum);
			sum = c[i]+d[j];
			sum = 0 - sum;
			sum2.push_back(sum); 
			count++;
		}
	sort(sum1.begin(), sum1.end());
	int begin = 0, last = 0;
	sum = 0;
	for(int i=0;i<count;i++)
	{
		begin = begin1(sum2[i], count);
		last = last1(sum2[i], count);
		if(begin != -1&&last != -1)
			sum = sum + last - begin + 1;
	}
	cout<<sum<<endl;
	return 0; 
}

C - TT 的神秘礼物

问题描述

给定一个 n 个数的数组 a[n],并用这个数组生成一个新数组 ans[n]。新数组定义为对于任意的 i, j 且 i != j,均有 ans[] = abs(a[i] - a[j]),1 <= i < j <= n。试求出这个新数组的中位数,中位数即为排序之后 (len+1)/2 位置对应的数字,’/’ 为下取整。

Input

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

Output

输出新数组 ans 的中位数

Example

Input

4
1 3 2 4
3
1 10 2

Output

1
8

解题思路

为了求出ans数组的中位数,似乎必须求出ans数组来,但这样运行时间无疑会超过限制,因此我们需要在不计算出ans数组的情况下计算出ans数组的中位数。比如说给定一个数P,如果他在ans数组中的名次等于中位数,那么这个数就是中位数。那么我们只需要计算a[ j ] - a[ i ] <= P的元素对数即可,又因为a[ j ] <= a[ i ] + P。因此我们在P已知的情况下,查找满足条件的 a[ j ] 的个数即可。此时我们可以使用二分查找寻找到满足条件的最大 j ,然后我们就可找到满足条件的数组对数。至于P的取值范围,显然是从0到ans的最大值,在枚举P时也需要用到二分查找,找到第一个满足数组对数大于等于中位数的P。

代码

#include <iostream>
#include <algorithm> 
#include <stdio.h>
using namespace std;

int *a;

int read()
{
    int s=0,w=1;
	char ch=' ';
    while(ch<'0' || ch>'9')
    {
    	ch=getchar();
        if(ch=='-') 
			w=-1;
    }
    while(ch>='0' && ch<='9')
    {
        s=s*10+ch-'0';
        ch=getchar();
    }
    return s*w;
}

int search(int target, int n) // satisfy condition: array[?] <= target and the last one
{
	int start = 0, end = n - 1;
	while (start <= end) 
	{
		int mid = (start + end) / 2;
		if (a[mid] <= target)
			start = mid + 1;
		else if (a[mid] > target)
			end = mid - 1;
	}
	
	if (start == 0)
		return -1;
 
	return start - 1;
}

int get(int n){
	a = new int[n];
	for(int i=0;i<n;i++)
		a[i] = read();
	sort(a, a+n);
	int max = a[n-1]-a[0];
	int rank = (n*n-n+2)/4;
	int x = 0, y = 0, z = 0;
	int start = 0, end = max, mid = 0;
	while(start <= end)
	{
		
		int mid = (start+end)/2;
		y = 0;
		for(int j=0;j<n;j++)
		{
			x = a[j] + mid;
			z = search( x , n) - j ;
			if(z > 0)
				y = y + z;
		}
		if( y >= rank)
			end = mid - 1;
		else if( y < rank)
			start = mid + 1;
	}
	return end+1;
}
int main()
{
	int n,sum;
	while(scanf("%d",&n)!=EOF)
	{
		sum = get(n);
		cout<<sum<<endl;
	}
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值