HDU 2430 Beans (单调队列+公式化简)

题意:给你n袋豆子,每袋都有w[i]个豆子,接着任选连续任意个袋子的豆子合在一起放入容量为p的多个袋子里(每个袋子必须放满),问剩余的豆子数<=k时,能放满最多的袋子的个数

 

题解:个数与p都比较大,直接模拟O(n^2),余数处理(dp)O(n*p)都会超时。

我们可以首先抽象出一个公式来:设前缀和为sum[i],则我们求(j+1,i)的豆子的数量时会使用sum[i]-sum[j]来求,而我们需要求的就是max(sum[i]-sum[j]),条件的是

(sum[i]-sum[j])%P<=k ——> (sum[i]%p-sum[j]%p+p)%p<=k

分类讨论:当sum[i]%p>=sum[j]%p    时就是    sum[i]%p-k <= sum[j]%p <= sum[i]%p

     当sum[i]%p<sum[j]%p我们可以反向来看转化为上面的情况

则我们枚举sum[i]时就只需要求出最前面一个满足条件的sum[j]就好(因为w[i]非负)

这样我们找到前缀和求余数数组rem,首先第一关键字rem升序第二关键字下标升序,接着我们枚举rem[现在]时就一定满足rem[现在]>=rem[队首],而rem[现在]-k是非递减的,所以我们可以单调队列来优化,队列里面rem从小到大,当 rem[现在]-k>rem[队首]则出队,最后rem[现在]入队(rem[现在]一定不小于队尾的rem)并向前走到比现在小的下标就好

 

#include<set>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<vector>
#include<string>
#include<cstdio>
#include<cstring>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
using namespace std;
#define eps 1E-8
/*注意可能会有输出-0.000*/
#define Sgn(x) (x<-eps? -1 :x<eps? 0:1)//x为两个浮点数差的比较,注意返回整型
#define Cvs(x) (x > 0.0 ? x+eps : x-eps)//浮点数转化
#define zero(x) (((x)>0?(x):-(x))<eps)//判断是否等于0
#define mul(a,b) (a<<b)
#define dir(a,b) (a>>b)
typedef long long ll;
typedef unsigned long long ull;
const int Inf=1<<28;
const double Pi=acos(-1.0);
const int Mod=1e9+7;
const int Max=1000100;
int que[Max];//单调队列
struct node
{
    int rem;//前缀余数
    ll pre;//前缀和
    int pos;
}res[Max];
bool cmp(struct node p1,struct node p2)
{
    if(p1.rem==p2.rem)
    return p1.pos<p2.pos;
    return p1.rem<p2.rem;//排序关键
}
ll nmax(ll a,ll b)
{
    return a>b?a:b;
}
ll Solve(int n,int p,int k)
{
    ll ans=-1ll;
    int top=0,bot=0;
    que[top++]=0;
    for(int i=1;i<=n;++i)//遍历x%p 找最小下标
    {
        while(top>bot&&res[i].rem-k>res[que[bot]].rem)//小于这个值的出队(因为之后这个值会变大)
            bot++;
        if(top>bot)
            ans=nmax(ans,(res[i].pre-res[que[bot]].pre)/(ll)p);
        while(top>bot&&res[que[bot]].pos>res[i].pos)//单调队列维护下标最小
            bot++;
        que[top++]=i;
    }

    return ans;
}
int main()
{
    int t,n,p,k,num,coun=0;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d %d %d",&n,&p,&k);
        res[0].rem=0,res[0].pre=0ll,res[0].pos=0;//注意加一个
        for(int i=1;i<=n;++i)
        {
            scanf("%d",&num);
            res[i].pre=res[i-1].pre+num;
            res[i].rem=res[i].pre%p;
            res[i].pos=i;
        }
        sort(res+1,res+n+1,cmp);
        printf("Case %d: %I64d\n",++coun,Solve(n,p,k));
    }
    return 0;
}

 

posted @ 2016-09-26 12:52  专注如一  阅读(431)  评论(0编辑  收藏  举报