Codeforces Round #469(Div.1 D) (Div. 2 F)Curfew(贪心)

Problem

  学校里有n(2 ≤ n ≤ 100 000)间宿舍,每间宿舍应住b(1 ≤ b ≤ 10 000)个人。但是囿于同学们会串宿舍,所以一开始第i间宿舍有 a i ( 0 a i 10 9 ) 个人。所有学生都在某间宿舍里,因此 a 1 + a 2 + . . . + a n = n b
  宿舍里有两个舍监。夜幕降临,他们就会开始搞事了。
  一个舍监会从1号宿舍巡到n号宿舍,另一个则会从n号宿舍巡到1号宿舍。每间宿舍只会被查一次。
  每次查房时,舍监会数学生,然后关灯锁门。如果这间宿舍的学生数≠b,舍监就会记下这间宿舍,这间宿舍就会gg了。
  每次他们查房前(包括他们降临在1号、n号宿舍的那一回合),每名学生可以在未被锁门的房间之间狂奔。他可以跑去与当前宿舍距离不超过d(1 ≤ d ≤ n - 1)的另一间宿舍。此外,学生可以藏到床底下,这样眼瞎的舍监就不会发现他。
  设x1表示1号舍监记下的房间数,x2表示2号舍监记下的房间数。学生团结一致互帮互助采用最优策略让你告诉他们max(x1,x2)的最小值。

Solution

  • 首先想一想只有一个从1号宿舍巡到n号宿舍的舍监怎么破。
  • 设当前宿舍为i。如果a[i]=b,当然不管它。
  • 如果a[i]>b,则要分类讨论:1)i<n,让多出来的学生去下一间宿舍;2)i=n,让多出来的学生藏到床底下。
  • 如果a[i]<b, 那么我们可以从后i*d间宿舍(亦即第i+1~第i * (d+1)间宿舍)派人过来救援。
  • 考虑一下如何实现这个贪心。

  • 对a求个前缀和,记为sum。设res表示前i-1间宿舍跑来第i间宿舍避难的人数。
  • 如果sum[min(1ll*n, 1ll*i*(d+1))] - sum[i-1] (当前宿舍和后i*d间宿舍原本的人数总和)+ res >= b,就代表我们可以把当前宿舍填满。此时,我们可能需要改变后i*d间宿舍的人数。
  • 强行改变可能会导致时间复杂度退化为 O ( n 2 ) 级别,需要想一种更好的方案。
  • 可以使res-=b。这就相当于我们从第i间宿舍、后i*d间宿舍以及原本避难的人当中“借”了b个人。这可能导致res<0。不过可以考虑一个例子:第i间宿舍有2人,而b为3;第j间宿舍在后i*d间宿舍当中,有4人;我们实质上从j跑了一个人到i;不过,当我们扫到j时,j有一个人要往j+1去避难,刚好抵掉了此次的res-=b。
  • 然后,如果sum[min(1ll*n, 1ll*i*(d+1))] - sum[i-1] + res < b,则第i间宿舍会被记下。
  • 最后,res+=a[i]。如果i最终没有被填满,反正要被记下了,不如倾巢而出去救援后面的宿舍;而如果i最终被填满了,配合上面的res-=b,实际上res-=b-a[i]

  • 考虑有两个舍监怎么破。
  • 实际上,我们可以直接按照上述方法贪心计算x1、x2的最小值,直接取max。
  • 这样看似捕星,因为x1愈小,x2肯定愈大;没理由让它们同时最小。
  • 但是可以考虑下图:
    这里写图片描述

  • 假设上图中的a[i]<b。最优策略肯定是让从绿色区间款款而来的人来填补;如果不够,则让蓝色区间的人狂奔过来填补;但如果还不够,则要让红色区间(另一个舍监的战场)的人铤而走险来填补了。

  • 这样看似会导致另一个舍监的战绩x2++,然而实际不会。
  • 注意本题的特殊性: a 1 + a 2 + . . . + a n = n b 。填补宿舍的人手是够的。
  • 这样一来,如果绿色、蓝色区间都不能填补i,则表明区间[1,mid]里的人<应有的人(mid*b);而红色区间里的人却>应有的人((n-mid+1)*b)。
  • 因此,在此种情况下,让红色区间的人填满了红色区间以后还绰绰有余。如下图所示:
    这里写图片描述

  • 最优策略定然存在一条分界线,分界线以左(紫色区间)的人用以填补区间[1,mid],分界线以右(黄色区间)的人用以填补区间[mid+1,n]。

  • 在上例中,分界线应在mid以右。黄色区间的人定然是可以填满区间[mid+1,n]的,因为他们可以待舍监到临前夕,让多余的人跑去下一间宿舍。
  • 因此,我们可以放心地用紫色区间的人填补i号宿舍,不用担心会影响到区间[mid+1,n]。

  时间复杂度: O ( n )

Code

#include <cstdio>
#include <algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;

const int N=1e5+1;
int i,n,d,b,a[N],sum[N],res,ans,x;

inline void work()
{
    if( sum[min(1ll*n, 1ll*i*(d+1))] - sum[i-1] + res >= b)
            res-=b;
    else    ans++;
    res+=a[i];
}

int main()
{
    scanf("%d%d%d",&n,&d,&b);
    fo(i,1,n) scanf("%d",&a[i]), sum[i]=sum[i-1]+a[i];

    fo(i,1,(n+1)/2) work();
    x=ans; ans=res=0;
    reverse(a+1,a+n+1);
    fo(i,1,n) sum[i]=sum[i-1]+a[i];
    fo(i,1,n/2)     work();

    printf("%d",max(x,ans));

}

猜你喜欢

转载自blog.csdn.net/qq_36551189/article/details/80935296