暑假考试题1:Dinner(二分+贪心)

题意:

一共N个人坐在坐在圆桌旁,服务员拿来了M份菜单。第i个人阅读菜单并点出自己喜欢的菜需要花费时间T[i]。 当一个人点完菜之后,就会把菜单传到他右手边的第一个人。 M份菜单是同时发出的,每个菜单只能同时被一个人阅读。 希望知道如何分发菜单,才能让点餐的总时间花费最少呢?

n<=5e4,m<=3e3

分析:

1.求的是最小值,考虑二分

2.圆桌说明是环,处理环的方法:复制序列+枚举断点

所以:二分一个答案mid,check的时候枚举以每一个点作为开头,依次向后传菜单能否满足mid(依次向后传,时间超了就菜单++)

那么总复杂度为n*n*logn

考虑优化:

1.真的需要枚举每一个点做开头吗?

eg:1 3 2 6 7   明显,如果mid=7,那么应该这样划分:1 3 2 / 6 / 7

然后会发现,枚举从1开始,和从4开始,从5开始,是等价的划分方法。

所以说,只需要划分到前缀和刚好大于mid的位置2,就足够了

2.每一次传菜单的时候一定要一个一个传吗?

我们希望快速找到一个位置,刚好传到它就超时了,那么可以通过二分找这个位置:记录前缀和,lower_bound确定位置

最后复杂度:n*logn*logn

#include<bits/stdc++.h>
using namespace std;
#define N 50005
int n,m,a[N],sum[N*2],bn;
bool work(int now,int x,int nx)
{
    int cnt=0;
    while(now<=nx){
        int pos=upper_bound(sum+1,sum+n*2,sum[now-1]+x)-sum;//优化2 
        if(now==pos) pos++;
        cnt++; now=pos;
        if(cnt>m) return false;
    }
    return true;
}
bool check(int x)
{
    int xx=lower_bound(sum+1,sum+n*2,x)-sum;//优化1
    for(int i=1;i<=xx;i++){
        if(work(i,x,n+i-1)) return true; 
    }
    return false;
}
int main()
{
    freopen("dinner.in","r",stdin);
    freopen("dinner.out","w",stdout);
    int maxn=0,minn=0,ans=0;
    scanf("%d%d",&n,&m);
    bn=n;
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        minn=max(a[i],minn);
        maxn+=a[i];
        sum[i]=sum[i-1]+a[i];
    } 
    for(int i=1;i<=n;i++)
    sum[n+i-1]=sum[n]+sum[i-1];
    int l=minn,r=maxn;
    while(l<r){
        int mid=(l+r)>>1;
        if(check(mid)) ans=mid,r=mid;
        else l=mid+1;
    }
    printf("%d
",ans);
}
/*
4 2 
1 2 3 4
ans 5
3 2 
1 5 10

*/
原文地址:https://www.cnblogs.com/mowanying/p/11396454.html