WEEK4 二分与贪心

DDL的恐惧:
题干:
ZJM 有 n 个作业,每个作业都有自己的 DDL,如果 ZJM 没有在 DDL 前做完这个作业,那么老师会扣掉这个作业的全部平时分。所以 ZJM 想知道如何安排做作业的顺序,才能尽可能少扣一点分。请你帮帮他吧!
INPUT:
输入包含T个测试用例。输入的第一行是单个整数T,为测试用例的数量。
每个测试用例以一个正整数N开头(1<=N<=1000),表示作业的数量。
然后两行。第一行包含N个整数,表示DDL,下一行包含N个整数,表示扣的分。
OUTPUT:
对于每个测试用例,您应该输出最小的总降低分数,每个测试用例一行。
SIMPLEINPUT:

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

SIMPLEOUTPUT:

0
3
5

分析:因为是等长的,每个任务都可以在一天这内完成,所以我们通过领先法证明是最优的,在T事件之前假设可以安排的DDL有N个,通过贪心策略安排出来的组合,因为是从大到小,从后往前安排,所以第一个安排进来的DDL不会影响到其他的DDL,所以假设我们将其中一个DDL替换,得到的解一定是小于通过贪心策略安排出来的DDL所以可得到代码如下:

、#include<iostream>
#include<algorithm>
#include<cstring> 
using namespace std;
struct work{
 int ddl;
 int value;
 bool operator<(const work& a)
 {
  return value>a.value;
 }
};
work works[1001];
int t[100000];
int main()
{
 int m;
 cin>>m;
 while(m--)
 {
  int n ; cin>>n;
  memset(t,0,sizeof(t));
  for(int i=1; i<=n; i++)
   cin>>works[i].ddl;
  for(int i=1; i<=n; i++)
   cin>>works[i].value;
  sort(works+1,works+n+1);
  int ans = 0;
  //从前向后遍历,因为前面是最大的将其从ddl向前搜索 
  for(int i=1;i <= n;i++)
  {
   bool k = false;
   for(int j=works[i].ddl;j>0;j--)
   {
    if(t[j]==0)
    {
     k = true;
     t[j]=1;
     break;
    }
   }
   if(!k)
   {
    ans += works[i].value;
   }
  }
  cout<<ans<<endl;
 }
 }

题目二:
二分查找:
题干:
ZJM 有四个数列 A,B,C,D,每个数列都有 n 个数字。ZJM 从每个数列中各取出一个数,他想知道有多少种方案使得 4 个数的和为 0。
当一个数列中有多个相同的数字的时候,把它们当做不同的数对待。
请你帮帮他吧!
INPUT:
第一行:n(代表数列中数字的个数) (1≤n≤4000)
接下来的 n 行中,第 i 行有四个数字,分别表示数列 A,B,C,D 中的第 i 个数字(数字不超过 2 的 28 次方)
OUTPUT:
输出不同组合的个数。
SIMPLEINPUT:

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

SIMPLEOUTPUT:

5

问题分析:
这个问题要分析时间复杂度的问题,有两种解法,
第一种是用一个哈希表存储A+B所有的可能性,因为数据是0~4000,所以开1600000的哈希表是可以满足使用的,再遍历C+D每次便利搜索有无他的相反数在桶里,hash表的搜索插入删除的时间为O(1)所以这种算法的事件复杂度为n²
第二种解法是用折半查找搜索的方法,将A+B的结果放在数组里排序好后,遍历C+D的值,二分查找第一个相反数的位置和最后一个相反数的位置。此方法的时间复杂度为O(N²logn)
这里是用第二种方法
这里介绍一下二分边界问题,二分的边界就是要将满足条件的数加入新的二分的范围内,将不满足的丢弃

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

int solve[4000][4];
vector<int> q;
int main()
{
	int num;
	cin>>num;
	int sum;
	int ans = 0;
	for(int i = 0;i <num;i++)
		for(int j = 0;j<4;j++)
			cin>>solve[i][j];
			
	for(int i = 0;i<num;i++)
	{
		for(int j = 0;j<num;j++)
		{
			sum = solve[i][0]+solve[j][1];
			q.push_back(sum);
			
		}
	}
	sort(q.begin(),q.end());
//	for(vector<int>::iterator it = q.begin();it<q.end();it++)
//	{
//		cout<<*it<<" ";
//	}
//	cout<<endl;
	for(int i = 0;i<num;i++)
	{
		for(int j = 0;j <num;j++)
		{
			sum = -(solve[i][2]+solve[j][3]);
			int l = 0;int r = q.size()-1;
			int first = -1,end = -1,mid;
			while(l<=r)
			{
				mid = (r+l)/2;
				if(q[mid]==sum)
				{
					first = mid; r = mid-1;
				}
				else if(q[mid]>sum)
				{
					r = mid - 1;
				}else{
					l = mid +1;
				}
			}
			l = 0;r = q.size()-1;
			while(l<=r)
			{
				mid = (r+l)/2;
				if(q[mid]==sum)
				{
					end = mid;
					//满足条件可以加入 
					l = mid+1;
				}
				else if(q[mid]>sum)
				{
					// 大于SUM证明我们要找的值在右边
					r = mid - 1;
				}else{
					l = mid +1;
				}
			}
			if(first!=-1) ans += end-first+1;
			
		}
	}
	cout<<ans;
}

题目三:
题干:
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 的中位数

SIMPLEINPUT:

4
1 3 2 4
3
1 10 2

SIMPLEOUTPUT:

1
8

题目分析:
分析问题,如果将所有的差值求出来,因为数据时1e9,所以一定会超时且超长,呢么我们怎样分析呢?
首先cat[]数组是一个单调递增的数组,而我们要找的位次也是单调递增的,所以在单调的区间上就可以用二分查找,而在这里有一个问题,假设某个数占据了中位数和其他几个位置,如果我们查询一个数,他的值大于中位数,则证明一定不是他,如果小于中位数,则证明有可能,因为我们只能查到比他小的数的个数
所以代码的组织形式
中间计算名次,因为是单调的数组,所以可以用二分找到最后一个使a[i]-a[j] < p 的值
当然在这里用的是O(n)的方法,从头开始遍历,如果a[i] - a[j] > p 那么a[i+1] -a[j] 也大于P,所以只需要一次遍历就可以查出所有的数,时间复杂度为O(n),总的是将复杂度为NlogN

在这里要注意边界问题,因为是向下取模的所以会遇到一些问题,我们一般总是让左边界进行mid的加运算,因为如果用右边界进行mid的减运算来缩小区间,可能会当值相邻的时候相加再平均,因为整数二分向下取,所以会因为左边界不缩小导致会无限循环。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
long long n,a[1000005];

int main()
{
	while(~scanf("%I64d",&n))
	{
		for(int i = 1; i <= n; i++)
		{
			scanf("%I64d",a+i);
		}
		sort(a+1,a+n+1);
		long long l = 0,r = a[n], mid, m = (n-1)*n/4,num;
		if((n*(n-1)/2)&1) m++;
		while(l<r)
		{
			mid = (l+r)>>1;
			int j = 1;//数组指针指向当前的满足条件的数,因为单调递增所以前面的数一定满足
			num = 0;
			for(int i = 2;i<=n;i++)
			{
				while(a[i]-a[j]>mid)
					j++;//如果a[i]-a[j]大于mid那么i+1自然而然的也大于密度,因为是递增的 
				num += (i-j);//减去大于的剩下的就是小于的,从1开始算到i不包括自身前面有i-1个数,j-1为大于mid的取值所以i-j就是小于的 
			}
			if(num>=m)//大于证明中点在左边,右边的边界变化 
			{
				r = mid; 
			 } 
			 else{
			 	l = mid+1;
			 }
		}
		printf("%I64d\n",l);
	}
 } 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值