Week4-作业

A - DDL 的恐惧


题意:

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

Input:

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

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


思路:

已知这是一道贪心算法的习题,需要找出该题的贪心指标。每个ddl的完成时间都是1,优先考虑得分高的作业。这时候需要将分数从高到低排序,如果分数相同,那么就考虑ddl,将相同得分的ddl从低到高排序。ddl和得分score可以构成一个结构体。找到了贪心指标,那就针对第i个ddl,利用bool数组该时间是否已经利用,根据t从前往后遍历,一旦找到空闲时间则将该时段安排。


总结:

这道题一开始只考虑了score排序,没有考虑相同score后需要进一步排序,样例虽然通过了,但还是wa了。对贪心指标一定要考虑严谨,对于一定的情况需要去证明其的合理性。


代码:

#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
#define _for(i,a,b) for(int i=a;i<b;i++)

struct ZJM
{
	int ddl;
	int score;
}z[1010];
bool vis[1010] = { false };
int cmp(const ZJM z1, const ZJM z2)
{
	if (z1.score == z2.score)
		return z1.ddl < z2.ddl;
	return z1.score > z2.score;

}

int main()
{
	int T,n;
	int a;
	cin >> T;
	_for(i, 0, T)
	{
		int count = 0;
		int a;
		cin >> n;
		_for(i, 0, n)
		{
			cin >> z[i].ddl;
		}
		_for(i, 0, n)
		{
			cin >> z[i].score;
		}
		sort(z, z + n,cmp);
		memset(vis, 0, sizeof(vis));
		_for(i, 0, n)
		{
			for (a = z[i].ddl; a >= 1; a--)
			{
				if (vis[a] == false)
				{
					vis[a] = true;
					break;
				}
			}
			if (a == 0) count += z[i].score;
		}
		cout << count << endl;
	}
	return 0;
	
}

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:
输出不同组合的个数。


思路:

一开始按学长所讲,枚举数列A+数列B,再枚举数列C+数列D。然后计算C+D的相反数在A和B中出现了多少次。不过在写的代码还是不小心将复杂度为O(N^4)

_for(i, 0, n*n)
	{
		_for(j, 0, n * n)
		{
			if (sumab[i] == -sumcd[j])
				count++;
		}
	}

很显然,肯定是TE的,这时候考虑了降低复杂度的二分方法,首先是将枚举数列C+D的所有数据保存的数组sumcd从小到大排序,然后从sumab依次进行枚举,初始化start和end,将start为0,end为(n-1)*(n-1),然后利用二分将sumab[i]与sumcd[mid]进行比较,找到相加等于0的最小的位置,然后再次进行循环,因为sumcd是有序的,若相同的数字出现多次,则一直向右加起来,这样遍历完sumab即可得到符合的个数。

代码:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
#define _for(i,a,b) for(int i=a;i<b;i++)
#define MAXN 4000
int a[MAXN], b[MAXN], c[MAXN], d[MAXN],sumab[MAXN*MAXN],sumcd[MAXN*MAXN];
int main()
{
	int count = 0;
	int n;
	cin >> n;
	_for(i, 0, n)
	{
		cin >> 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++)
		{
			sumab[k] = a[i] + b[j];
			sumcd[k] = c[i] + d[j];
			k++;
		}
	}
	sort(sumcd, sumcd + k);
	for (int i = 0; i <k; i++)
	{
		int start =0, end =k-1;
		while (start < end)
		{
			int mid = (start + end) >> 1;
			if (sumab[i] + sumcd[mid] >= 0) 
			{
				end = mid;
			}
			else start = mid + 1;
		}
		while (sumab[i] + sumcd[start] == 0 && start < k)
		{
			count++;
			start++;
		}
	}
	cout << count << endl;
	return 0;

}

TT 的神秘礼物

题意:

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

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

output:
输出新数组 ans 的中位数.


思路:

将cat数组从小到大排列,中位数的名次是已知的(n*(n-1)/2+1)/2 对每一次二分得到的名次与中位数的名次比较,如果名次小于中位数,就代表这个数比中位数小,否则就比中位数大。然后目标是求这个二分值P,再求名次。ans中的数组元素是cat[j]-cat[i](i<j),然后就变成了就是求满足cat[j]-cat[i]<=P的所有二元组对数。通过移项,得到cat[j]<=P+cat[i],接着就枚举i满足j的个数,就得到了名次。然后这个名次是利用二分查找,满足最后一个cat[j]>=P+cat[i]的位置,就得到j的最大值;名次等于每一次j的最大值减去i的累加和。求出名次再和中位数名次比较,直到找到中位数就行了。

总结:

1.这道题比较绕,由于要进行两次二分,进行首轮二分的时候想通过网上找找有没有比较实用的二分函数:于是我找到了这个
(1)、binary_search(beg,end,val)
返回一个bool变量,以二分法检索的方式在[beg,end]之间查找val,找到返回true,找不到返回false。
(2)、lower_bound(beg,end,val)
返回一个迭代器,指向非递减序列[first, last)中的第一个大于等于(>=)val的位置。
(3)、upper_bound(beg,end,val)
返回一个迭代器,指向非递减序列[first, last)中的第一个大于 (>) val的位置。
原文链接:https://blog.csdn.net/xzymmd/article/details/83902281
这些都是利用二分来找到位置。通过这些函数的原理让我知道了如何进行二次二分。
2.由于time limit,一开始用的cin导致多次TE,找了好几遍以为是我二分的原因,修改了一下还是TE,然后尝试了一下scanf就通过了。
在这里插入图片描述
后来请教了朋友学到了ios::sync_with_stdio(false)可以提高cin的效率。
总之这道题让我受益颇多,学到了很多实用的知识。

代码:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

int main()
{
	int n;
	int start, end, mid, median, rank,mid1,ans,ans1;
	while (cin >> n)
	{
		int cat[100002];
		for (int i = 0; i < n; i++)
		{
			scanf("%d",&cat[i]);
		}
		sort(cat, cat + n);
		ans=-1;
		start = 0;  //绝对值最小只为0
		end = cat[n - 1] - cat[0]; //最大的数
		median = (1 + n * (n - 1) / 2) / 2; //中位数所在位置
		while (start <= end)
		{
			mid = (start + end) / 2;
			rank = 0;  //初始化位置
			for (int i = 0; i < n; i++)
			{
				int temp=cat[i] + mid; 
				int l = 0, r = n-1;
				ans1=-1;
				while (l <= r)
				{
					mid1 = (l + r)/2;
					if (cat[mid1] <= temp)
					{
						ans1=mid1;
						l=mid1+1;
					}

					else r = mid1 - 1;
				}
				if(ans1!=-1)
					rank+=ans1-i;
			}
			if(rank<median)
			{
				start=mid+1;
			}
			else
			{
				ans=mid;
				end=mid-1;
			}
			
			
		}
		cout << ans << endl;

	}

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值