第四周 贪心与二分

A : 摆放垃圾桶

题目描述
滨海公园旁边新铺了一条路,把这条路分成n段,依次编号为1…n。为了防止游客把垃圾扔到海里,war要在路上放一些垃圾桶🚮。政府提出了m个要求,每个要求包含三个整数l,r,k,表示路段l到r之间至少有k个垃圾桶 。垃圾桶放太多不仅浪费资源,也影响风景,所以每个路段至多可以放一个垃圾桶。为了环保♻️,war最少要放多少个垃圾桶才能满足要求?

输入描述
第一行为n(1≤n≤3*10^4)(1≤n≤3∗10
4
),表示路段数。

第二行为m(1≤m≤5000)(1≤m≤5000),表示要求数。

下面m行,每行描述一条建议l,r,k,用一个空格分隔,(1≤l≤r≤3*10^4,k≤r-l+1)(1≤l≤r≤3∗10
4
,k≤r−l+1)。

输出描述
输出只有一个数,为满足政府所有的要求,需要的垃圾桶的最少数量。

提示
贪心

Case 1
Input
9
4
1 4 2
4 6 1
8 9 2
3 5 2
Output
4

代码

#include<bits/stdc++.h>
using namespace std;
struct tree
{
    int b,e,t;
}a[30001];
int n,h,ans;
bool m[30001];
bool cmp(tree x,tree y)
{
    return x.e<y.e;
}
int main()
{
    cin>>n>>h;
    for(int i=1;i<=h;i++) cin>>a[i].b>>a[i].e>>a[i].t;
    sort(a+1,a+h+1,cmp);
    for(int i=1;i<=h;i++)
    {
        int cnt_ans=0;
        for(int j=a[i].b;j<=a[i].e;j++)
        {
            if(m[j]) cnt_ans++; 
        }
        if(cnt_ans>=a[i].t) continue;
        for(int j=a[i].e;j>=a[i].b;j--)
        { 
            if(!m[j])
            { 
                m[j]=1;
                cnt_ans++;
                ans++;
                if(cnt_ans==a[i].t) break;
            }
        }
    }
    cout<<ans;
    return 0;
}

B : 看电影

题目描述
终于可以随意出入校园啦!于是,n个人决定去看电影🎬。每个人都要先去A影院再去B影院。第i个人在A,BA,B影院的观影时间分别为a_i,b_ia
i

,b
i

。按照疫情防控要求,每个影院最多允许1人同时观影。那么怎样安排这n个人的观影顺序,能使所有人都在A,BA,B影院观影完的总时间最少。

输入描述
第一行为n(0<n<1000)(0<n<1000),表示人数。

第二行n个数,第i个数为a_i(1≤a_i≤350)a
i

(1≤a
i

≤350),中间有空格。

第三行n个数,第i个数为b_i(1≤b_i≤350)b
i

(1≤b
i

≤350),中间有空格。

输出描述
输出只有一个数,为最少的总时间。

提示
贪心,可能需要了解Johnson法则

Case 1
Input
5
3 5 8 7 10
6 3 2 5 8
Output

代码

//#include <stdafx.h> 
#include <iostream> 
using namespace std; 

class Jobtype
{
	public:
		int operator <=(Jobtype a) const
		{
			return(key<=a.key);
		}
		int key,index;
		bool job;
};
 
int FlowShop(int n,int a[],int b[],int c[]);
void BubbleSort(Jobtype *d,int n);//本例采用冒泡排序
 
int main()
{
	int N;
	cin>>N;
	int a[1000] ;
	int b[1000] ;
	for (int i=0;i<N;i++){
		cin>>a[i];
	}
	for (int i=0;i<N;i++){
		cin>>b[i];
	}
	int c[N];
 
	int minTime =  FlowShop(N,a,b,c);
 
	/*cout<<"作业在机器1上的运行时间为:"<<endl;
	for(int i=0; i<N; i++)
	{
		cout<<a[i]<<" ";
	}
	cout<<endl;
	cout<<"作业在机器2上的运行时间为:"<<endl;
	/*for(int i=0; i<N; i++)
	{
		cout<<b[i]<<" ";
	}
	cout<<endl;*/
 
	cout<<minTime<<endl;
	/*cout<<"编号从0开始,作业调度的顺序为:"<<endl;
	for(int i=0; i<N; i++)
	{
		cout<<c[i]<<" ";
	}
	cout<<endl;*/
	return 0;
}
 
int FlowShop(int n,int a[],int b[],int c[])
{
	Jobtype *d = new Jobtype[n];
	for(int i=0; i<n; i++)
	{
		d[i].key = a[i]>b[i]?b[i]:a[i];//按Johnson法则分别取对应的b[i]或a[i]值作为关键字
		d[i].job = a[i]<=b[i];//给符合条件a[i]<b[i]的放入到N1子集标记为true
		d[i].index = i;
	}
 
	BubbleSort(d,n);//对数组d按关键字升序进行排序
 
	int j = 0,k = n-1;
 
	for(int i=0; i<n; i++)
	{
		if(d[i].job)
		{
			c[j++] = d[i].index;//将排过序的数组d,取其中作业序号属于N1的从前面进入
		}
		else
		{
			c[k--] = d[i].index;//属于N2的从后面进入,从而实现N1的非减序排序,N2的非增序排序
		}
	}
 
	j = a[c[0]];
	k = j+b[c[0]];
	for(int i=1; i<n; i++)
	{
		j += a[c[i]];//M1在执行c[i]作业的同时,M2在执行c[i-1]号作业,最短执行时间取决于M1与M2谁后执行完
		k = j<k?k+b[c[i]]:j+b[c[i]];//计算最优加工时间
	}
 
	delete d;
	return k;
}
 
//冒泡排序
void BubbleSort(Jobtype *d,int n)
{
	int i,j,flag; 
	Jobtype temp;
 
	for(i=0;i<n;i++){  
		flag = 0;  
		for(j=n-1;j>i;j--){  
			//如果前一个数大于后一个数,则交换  
			if(d[j]<=d[j-1]){  
				temp = d[j];  
				d[j] = d[j-1];  
				d[j-1] = temp;  
				flag = 1;  
			}  
		}  
		//如果本次排序没有进行一次交换,则break,减少了执行之间。  
		if(flag == 0){  
			break;  
		}  
	}
}

C : 划分石头

题目描述
海边有很多好看的石头🪨,war把它们收集起来依次排成一排,一共n个,第i个石头的重量为w_iw
i

。他想将其分成m段,每一段连续,并且重量和最大的段的和最小。请你帮帮他~

输入描述
第一行为n(1≤n≤10^5)(1≤n≤10
5
),m(m≤n)(m≤n)。

第二行n个数,第i个数为w_i(0≤wi≤10^9)w
i

(0≤wi≤10
9
),中间有空格。

输出描述
输出只有一个数,重量和最大的段的和。

提示
二分答案

Case 1
Input
6 3
4 2 4 5 1 1
Output
7

代码


#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<string>
#include<set>
#include<map>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1e6+10;
const double eps=1e-5;
int a[maxn];
int n,m;
bool is_true(int x)
{
    int s=0;
    int t=0;
    for(int i=0;i<n;i++){
        if(a[i]>x)
            return 0;
        if(s+a[i]>x){
            s=a[i];
            t++;
            if(t>m-1)
                return 0;
        }
        else
            s+=a[i];
    }
    return 1;
}
int solve()
{
    int x=0,y=0;
    for(int i=0;i<n;i++){
        y+=a[i];
       // if(x<a[i])
         //   x=a[i];
    }
 
    while(x<y){
        int m=x+(y-x)/2;
        if(is_true(m))
                y=m;
        else
                x=m+1;
    }
    return x;
}
int main()
{
    cin>>n>>m;
    for(int i=0;i<n;i++)
        scanf("%d",&a[i]);
    int ans=solve();
    cout<<ans<<endl;
    return 0;
}


D : 捡贝壳

题目描述
war发现海边还有好看的贝壳🐚,他把n个贝壳依次排成一排,第i个贝壳的体积为v_iv
i

。war觉得平均体积最大,且长度不小于m的一段贝壳最好看。请你帮他找出这一段~

输入描述
第一行为n(1≤n≤10^5)(1≤n≤10
5
),m(m≤n)(m≤n)。

第二行n个数,第i个数为v_i(0≤vi≤2000)v
i

(0≤vi≤2000),中间有空格。

输出描述
输出一个整数,表示这个平均体积的1000倍。不用四舍五入,直接输出。

提示
二分答案,可能需要用到前缀和思想

Case 1
Input
12 6
1
1
1
1
1
1
1
1
1
1
1
1
Output
1000

代码

#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int n,f;
int cow[N];
double sum[N];

bool check(double avg)
{
    //预处理前缀和数组
    for(int i = 1; i <= n; i++)
        sum[i] = sum[i-1] + cow[i]  - avg;
    
    double minv = 0; // 记录sum[0..i]中最小值
    for(int i = 0, j = f; j <= n; j++,i++)
    {
        minv = min(minv,sum[i]);
        // 如果满足,则说明存在一段非负和的子段
        if(sum[j] >= minv) return true;
    }
    
    return false;
}

int main()
{
    scanf("%d%d",&n,&f);
    
    double l = 0, r = 0;
    for(int i = 1 ; i <= n; i++)
    {
        scanf("%d",cow + i);
        r = max(r, (double)cow[i]);
    }
    
    while(r - l > 1e-5)
    {
        double mid = (l + r) / 2;
        if(check(mid)) l = mid;
        else r = mid;
    }
    
    printf("%d", (int)(r * 1000));
    
    return 0;
}

思路

和种树问题一样,贪心加排序。洛谷有一样的题
链接

在这里插入图片描述
在这里插入图片描述

二分思想和前缀和:

二分平均值:假设当前二分的平均值为mid,那么在序列中寻找是否有平均值>=mid 且 l e n > = F len >= F len>=F的子段。我们可以让序列每个数减去mid,则问题转换为在序列中寻找一个 l e n > = F len >= F len>=F 且 子段和为非负的子段。

可以用前缀和预处理出减去mid后的序列的前缀和。
设前缀和数组为sum,则我们要找到一个子段 [ i , j ] ( j − i > = F ) , s u m [ j ] − s u m [ i ] > = 0 [i,j](j - i >= F),sum[j] - sum[i]>=0 i,j,sum[j]−sum[i]>=0,
则说明当前mid可行,设置 l = m i d l=mid l=mid继续判断更大的平均值.,否则设置 r = m i d r=mid r=mid缩小平均值。

由于数据是 1 0 5 10^5 10
5
级别,显然通过枚举 i , j i,j i,j的做法会超时 ( O ( n 2 ) ) (O(n^2)) (O(n
2
))。

事实上我们可以做如下转换:
∃ ( s u m [ j ] ≥ s u m [ i ] ) ⇒ s u m [ j ] ≥ m i n { s u m [ k ] , 0 ≤ k ≤ i } \exists( sum[j] \ge sum[i]) \Rightarrow sum[j] \ge min{sum[k], 0 \le k \le i} ∃(sum[j]≥sum[i])⇒sum[j]≥min{sum[k],0≤k≤i}

利用双指针 i , j i,j i,j便可以在 O ( n ) O(n) O(n)内检查是否有满足条件的连续序列,
初始时 i = 0 , j = F i = 0, j = F i=0,j=F,并设 m i n V = m i n { s u m [ k ] , 0 ≤ k ≤ i } minV = min{sum[k], 0 \le k \le i} minV=min{sum[k],0≤k≤i},每次迭代时更新 m i n V minV minV并判断当前子段是否非负,如果不是则同时后移 i , j i,j i,j继续判断。
显然二分边界为 l = 0 , r = m a x ( c o w [ i ] ) l = 0,r = max(cow[i]) l=0,r=max(cow[i])。

另一个题:
【动态规划】流水作业调度问题与Johnson法则
这里有详细介绍,有点复杂。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值