贪心问题,二分

贪心问题

DDL的恐惧

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

  • 输入输出
    输入包含T个测试用例。输入的第一行是单个整数T,为测试用例的数量。每个测试用例以一个正整数N开头(1<=N<=1000),表示作业的数量。然后两行。第一行包含N个整数,表示DDL,下一行包含N个整数,表示扣的分。
    对于每个测试用例,您应该输出最小的总降低分数,每个测试用例一行。

解题

  • 两个贪心
    第一个贪心,分高的作业优先完成。
    第二个贪心,被选中的作业尽量在它最后期限完成,降低对其它作业的影响。

  • 算法设计
    对作业数组a[ n ]按照分数a[ i ].score单调递减排序。
    定义一个二维数组b[ 2 ][ n ]记录被选中的作业和被选中的完成时间,即b[ 0 ][ i ] =1 表示第 i 天被选中完成某个作业,b[ 1 ][ j ] = 1 表示作业 j 被选中。
    从第一个(已经排序,第一个分最高)开始,如果作业 i 的最晚DDL没被占用(没有用来完成其它作业),则j = DDL;否则,j - -(j > 0)直到找到或不存在这样的时间 j 。同时,b数组记录第 i 个作业已经被选中,且当前时间 j 被选中。

代码

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

bool b[2][1001];
int n;

struct zjm{
	int s,ddl;
}a[1001];
bool cmp(zjm u,zjm v){
	return u.s>v.s;
}
void fun(){
	memset(b,0,sizeof(b));
	sort(a,a+n,cmp);
	for(int i=0;i<n;i++){
		for(int j=a[i].ddl;j>0;j--){
			if(!b[0][j]){
				b[0][j]=1;
				b[1][i]=1;
				break;
			}
		}
		
	}
	
	int sum=0;
	for(int i=0;i<n;i++){
		if(!b[1][i])
			sum+=a[i].s;
	}	
	cout<<sum<<endl;
}
int main(){
	int t;
	cin>>t;
	for(int i=0;i<t;i++){
		cin>>n;
		for(int j=0;j<n;j++)
			cin>>a[j].ddl;
		for(int j=0;j<n;j++)
			cin>>a[j].s;
		fun(); 
	}
}

二分问题

四个数列

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

解题

  • 暴力解法
    枚举每个ABCD,求和。时间复杂度O(n^4)

  • 二分优化
    AB一组,CD一组。分别枚举,记录AB的和,和CD的和。如果AB和=sum的方法有x种,CD和=
    -sum的方法有y种,则ABCD=0的方法有xy种。
    那么我们只需要记录所有的xy相加的和即可,复杂度O(n^2)。

代码

#include<iostream>
#include<algorithm>
#include<map>
using namespace std;
map<int,int> mp;
int a[4000],b[4000],c[4000],d[4000],sum1[16000000],sum2[16000000];
int main(){
	int n,k=0;
	int ans=0,sum;
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>a[i]>>b[i]>>c[i]>>d[i];
	}
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++){
			sum1[k]=a[i]+b[j];
			sum2[k++]=c[i]+d[j];
		}
	}
	 sort(sum2, sum2+k);
	int l,r,mid;
	ans = 0;//记录满足要求的个数
    for(int i = 0;i < k;i++)
    {
        l = 0, r = k;
        while(l < r)
        {
            mid = (l + r) / 2;
            if(sum1[i] + sum2[mid] < 0)
                   l = mid + 1;
            else
                   r = mid;
            while(sum1[i] + sum2[l] == 0 && l < k)
            {
                ans++;
                l++;
            }
        }
    }

	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 位置对应的数字,’/’ 为下取整。
  • 输入输出
    多组输入,每次输入一个 N,表示有 N 个数,之后输入一个长度为 N 的序列 cat, cat[i] <= 1e9 , 3 <= n <= 1e5
    输出新数组 ans 的中位数

解题

  • 新数组
    实际上,新数组等于cat数组中任意两个索引不同的数求差值。

  • 普通解法
    枚举数组求新数组,复杂度O(n^2)。

  • 二分优化
    刚刚我们是从已知数组中找中位数(需要先求出这个数组),换个思路,不求出数组,直接使用二分枚举数mid证明它是中位数。

  • 中位数
    中位数是一个单调数组中间的数,即第( n * ( n - 1 ) / 2 + 1) / 2 个数,我们可以认为中位数的名次是MID = ( n * ( n - 1 ) / 2 + 1) / 2。

  • mid的范围
    要枚举一个数mid求它的名次ans是否等于MID,首先要知道它的范围。最小等于0,最大等于数组cat中最大数-最小数。PS:在枚举中,使用二分。为找出最大数和最小数,cat数组需要排序。

  • mid的名次
    我们知道新数组可以用cat[ j ] - cat[ i ] ( j > i,其中cat排序后单调递增)来表示,有几个(cat[ i ], cat[ j ])这样的组合使cat[ j ] - cat[ i ] <= mid成立,就是mid的名次。

  • 名次的计算
    枚举 i ,找出有多少个 j 使cat[ j ] - cat[ i ] <= mid成立。
    j > i,j 一定在 i 后面。
    在单调数组中,只有找到第一个 j 不满足条件,当前 j 后面的一定也不满足。
    也就是说找出最后一个满足 cat[ j ] <= cat [ i ] + mid的 j ,ans_i = j - i就是cat[ i ]时满足的个数。
    枚举所有 i ,求出总和 ans 就是 mid 的名次。

代码

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

int n,MID;
int a[100001];

bool fun(int mid){
	int m,l,r,x,ans=0;
	for(int i=1;i<=n;i++){
		x=a[i]+mid;
		l=i;r=n;
		while(l<=r){
			m=(l+r)/2;
			if(a[m]>x){
				r=m-1;
			}
			else{
				l=m+1;
			}
		}
		ans+=r-i;
	}
	return ans<MID;
}
int main(){
	while(scanf("%d",&n)==1){  
	
	for(int i=1;i<=n;i++)
		scanf("%d",a+i);
	sort(a+1,a+n+1);
	MID=n*(n-1)/2;
	MID=(MID+1)/2;
	int lp=0,rp=a[n]-a[1],mp;
	while(lp<=rp){
		mp=(lp+rp)>>1;
		if(fun(mp)){
			lp=mp+1;
		}
		else{
			rp=mp-1;
		}
	} 
	printf("%d\n",lp);
	}
}
发布了7 篇原创文章 · 获赞 2 · 访问量 201

猜你喜欢

转载自blog.csdn.net/Schrodingerofcat/article/details/105089884