程序设计思维与实践 Week4 Blog

一、大纲

本周作业与实验题目如下:

  • DDL的恐惧(贪心算法)
  • 四个数列(整数二分)
  • TT的神秘礼物(二分答案)

二、逐个击破

1.区间选点II(差分约束系统)

题目描述

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

  1. Input

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

  1. Output

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

题目分析

  这道题目依旧是贪心算法的应用,而贪心准则为每次完成deadline最近的一个任务,下面是该种贪心准则的算法正确性的证明:

  1. 每个deadline完成所需时间均为1,因此优先考虑价值高的任务;
  2. 不难发现根据𝑡i从后往前遍历,可以保证第𝑖𝑖个deadline的安排时间对其他deadline的影响最小,因此结果更优;
  3. 严谨的数学证明可参考之前的交换证明方法,无太大区别
  • 从后往前枚举每一天,给每一天安排任务;
  • 枚举到第x天时,将所有 t i = x t_i=x ti=x 的deadline加入最大堆中;
  • 再从最大堆中选取一个价值最大的deadline安排在第x天。

  下面是题目的全部代码:

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

struct task
{
	int ddl;
	int minus;
	bool operator<(task& b)
	{
		return minus > b.minus;
	}
};
int main()
{
	bool day[1050];
	int T;
	int max = 0;
	scanf("%d", &T);
	for (int i = 0; i < T; i++)
	{
		int ddl[1050];
		int score[1050];
		vector<task>ts;
		for (int i = 0; i < 1050; i++)
		{
			day[i] = false;
		}
		int sum = 0;
		int n;
		scanf("%d", &n);
		for (int j = 0; j < n; j++)
			scanf("%d", &ddl[j]);
		for (int j = 0; j < n; j++)
			scanf("%d", &score[j]);
		for (int k = 0; k < n;k++)
		{
			task theT;
			theT.ddl = ddl[k];
			theT.minus = score[k];
			ts.push_back(theT);
		}
		sort(ts.begin(), ts.end());
		for (int k = 0; k < n; k++)
		{
			bool got = false;
			int last = ts[k].ddl;
			for (int j = last; j >= 1; j--)
			{
				if (day[j] == false)
				{
					day[j] = true;
					got = true;
					break;
				}
			}
			if (got == false) 	sum += ts[k].minus;
		}
		printf("%d\n", sum);
	}
	return 0;
}

2.四个数列(整数二分)

题目描述

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

  1. Input

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

  1. Output

  输出不同组合的个数。

题目分析

  • 枚举AB数列各种和,放入数组AB当中;

  • 枚举CD数列各和,每得到一个和搜索其相反数出现在AB中的位置——第一个和最后一个,这样可以比较高效地得到相反数的个数——即对应解的个数。注意两者区别:前者在满足条件之后缩小右边界,后者则是扩大左边界。而其中求取第一个和最后一个出现位置的方法就是利用二分法来降低复杂度 O ( l o g n ) O(logn) O(logn)

      该题全部解决代码如下:

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

int AB[16000050]; 

int _first(int num,int n)//此处尽量不要用传值参数,用全局变量 
{//寻找等于num的最小索引 
	int l=0,r=n-1,index=-1;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(AB[mid]==num)
		{
			index = mid;
			r = mid-1; 
		}
		else if(AB[mid]<num) l=mid+1;
		else r=mid-1;
	}
	return index;
}


int _last(int num,int n)
{//寻找等于num的最大索引 
	int l=0,r=n-1,index=-1;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(AB[mid]==num)
		{
			index = mid;
			l = mid+1; 
		}
		else if(AB[mid]>num) r=mid-1;
		else l=mid+1;
	}
	return index;
}

int A[4050];
int B[4050];
int C[4050];
int D[4050];

int main()
{
	int n;
	int cnt=0;
	scanf("%d",&n);
	for(int i=0;i<n;i++)
		scanf("%d%d%d%d",&A[i],&B[i],&C[i],&D[i]);
	int k=0; 
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n;j++)
		{
			AB[k]=(A[i]+B[j]);
			k++;
		}
	}
	sort(AB,AB+k);
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n;j++)
		{
			int sum=C[i]+D[j];
			int first=_first(-sum,k);
			int last=_last(-sum,k);
			if(first==-1||last==-1)continue;
			else
				cnt+=last-first+1; 
		}
	}
	printf("%d",cnt);	
	return 0;
}

3.TT的神秘礼物(二分答案)

题目描述

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

  1. Input

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

  1. Output

  输出新数组 ans 的中位数

题目分析

   由于题目所求答案也是单调的,所以也可以进行二分求解,具体操作如下:

  • 考虑到数列中全部数不小于0,因此中位数必定是在0到最大值之间——在将数组排序后,最大值即为 x [ n ] − x [ 1 ] x[n]-x[1] x[n]x[1]。因此对此区间进行二分,最终得到第一个排名大于等于中间排名的数——即为中位数。
  • 因此需要计算一个数的排名。在有序的前提下,即 x j − x i < = x ( j > i ) xj - xi <= x(j>i) xjxi<=x(j>i)。可以直接枚举每一个i,考虑到单调递增,因此再次对于 x [ j ] x[j] x[j]进行二分处理,得到最后一个满足条件的 x [ j ] x[j] x[j],这样就可以得到满足条件的二元组的个数,再进行求和即可。

   所以这道题目具体的代码如下:

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

int ans;

int cal_cnt(int P, int* a, int n)
{
	int cnt = 0;
	for (int i = 0; i < n - 1; i++)
	{
		int l = i + 1, r = n - 1, index = -1;
		while (l <= r)
		{
			int mid = (l + r) >> 1;
			if (a[mid] <= a[i] + P)
			{//寻找满足条件的最大的索引 
				index = mid;
				l = mid + 1;
			}
			else r = mid - 1;
		}
		if (index != -1) cnt += index - i;
	}
	return cnt;
}


void judge_mid(int* a, int n)//此处尽量不要用传值参数,用全局变量 
{//寻找等于num的最小索引 
	int l = 0, r = a[n - 1] - a[0];//这个地方由于求的是新数组的最大值,a[0]可能为负,所以必须 -a[0] 
	int result = (n * (n - 1) / 2 + 1) / 2;
	int P;
	while (l <= r)
	{
		P = (l + r) >> 1;
		int ord = cal_cnt(P, a, n);
		if (ord >= result)
		{
			r = P - 1;
			ans = P;
		}
		else l = P + 1;
	}
	printf("%d\n", ans);
}

int main()
{
	int N;
	while (~scanf_s("%d", &N))
	{
		int cat[100005];
		for (int i = 0; i < N; i++)
			scanf_s("%d", &cat[i]);
		sort(cat, cat + N);
		judge_mid(cat, N);
	}
	return 0;
}

题目总结

这道题目在判断是否为中位数的函数中,由于求的是新数组的最大值,然而 a [ 0 ] a[0] a[0]可能是负的,所以应该让 r = a [ n − 1 ] − a [ 0 ] r=a[n-1]-a[0] r=a[n1]a[0]作为右端点进行二分操作,以后要注意这些特殊边界和是否会出现数组越界的问题!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值