[bzoj3721]Final Bazarek

题目描述

有n件商品,选出其中的k个,要求它们的总价为奇数,求最大可能的总价。

做法

首先肯定要取一个奇数,所以一定取最大的奇数。
一个奇数都没有可以直接-1了。
接下来奇数只能两个两个取,而偶数可以一个一个取。
而且肯定从大到小,所以先排个序。然后num[x]表示取x对奇数前缀和,sum[x]表示取x个偶数前缀和。
假设要取k个,而且我们取了x对奇数,贡献是num[x]+sum[k-2x]
所有询问当然根据k从小到大做啦,然后考虑决策单调性,即如何找到最优x。
对于两个合法决策x和y满足 x<y (注意我们做的过程只能考虑合法的决策,合法决策x需满足k-2x>=0且k-2x<=top,top是偶数个数,前者的满足可以每次k改变时把新的合法决策加入,后者则在计算答案时才去检验是否合法),随着k的变大,y决策会越来越比x优(因为我们知道k改变时sum[k-2y]的增量会比sum[k-2x]大),因此可以通过二分计算两个决策在k达到多少时y会比x优。
而当y比x优时,y便永远的比x优了,x也就变成了冗余状态。
考虑维护单调队列,维护交点的单调递增(因为k递增),每次取答案时不断检验队头是否合法(你会发现因为按小到大加入队列中,在队列前面的越容易非法),假如队列空了该询问肯定是-1。
详见代码,复杂度O(n log n)

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int maxn=1000000+10;
struct dong{
    int k,id;
} ask[maxn];
int a[maxn],b[maxn],c[maxn],dl[maxn],xy[maxn],sta[70];
ll ans[maxn],num[maxn],sum[maxn];
int i,j,k,l,t,n,m,tot,top,mx,head,tail,cnt;
bool czy;
int read(){
    int x=0,f=1;
    char ch=getchar();
    while (ch<'0'||ch>'9'){
        if (ch=='-') f=-1;
        ch=getchar();
    }
    while (ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
bool cmp(int a,int b){
    return a>b;
}
bool cmp2(dong a,dong b){
    return a.k<b.k;
}
void write(ll x){
    if (x<0){
        putchar('-');
        x=-x;
    }
    if (!x){
        putchar('0');
        putchar('\n');
        return;
    }
    cnt=0;
    while (x){
        sta[++cnt]=x%10;
        x/=10;
    }
    while (cnt){
        putchar('0'+sta[cnt]);
        cnt--;
    }
    putchar('\n');
}
int getxy(int x,int y){
    //if (2*y>2*x+top) return 2*y;
    int l=2*y,r=2*x+top+1,mid;
    while (l<r){
        mid=(l+r)/2;
        if (num[x]+sum[mid-2*x]<=num[y]+sum[mid-2*y]) r=mid;else l=mid+1;
    }
    return l;
}
void insert(int x){
    while (head<tail&&xy[tail]>=getxy(dl[tail],x)) tail--;
    dl[++tail]=x;
    if (head<tail) xy[tail]=getxy(dl[tail-1],x);
}
int main(){
    freopen("3721.in","r",stdin);freopen("3721.out","w",stdout);
    czy=1;
    n=read();
    fo(i,1,n){
        a[i]=read();
        if (a[i]%2==1) b[++tot]=a[i];else c[++top]=a[i];
    }
    if (tot==0) czy=0;
    m=read();
    if (!czy){
        fo(i,1,m) write(-1);
        return 0;
    }
    sort(b+1,b+tot+1,cmp);
    mx=b[1];
    fo(i,2,tot) b[i-1]=b[i];
    tot--;
    sort(c+1,c+top+1,cmp);
    fo(i,1,tot/2) num[i]=num[i-1]+(ll)b[i*2-1]+(ll)b[i*2];
    tot/=2;
    fo(i,1,top) sum[i]=sum[i-1]+(ll)c[i];
    fo(i,1,m){
        ask[i].k=read();
        ask[i].k--;
        ask[i].id=i;
    }
    sort(ask+1,ask+m+1,cmp2);
    j=0;
    head=1;
    fo(i,1,m){
        while (j<=tot&&j*2<=ask[i].k){
            insert(j);
            j++;
        }
        while (head<=tail&&ask[i].k-2*dl[head]>top) head++;
        while (head<tail&&ask[i].k>=xy[head+1]) head++;
        if (head>tail) ans[ask[i].id]=-1;
        else ans[ask[i].id]=(ll)mx+num[dl[head]]+sum[ask[i].k-2*dl[head]];
    }
    /*if (!czy){
        fo(i,1,m) ans[i]=-1;
    }*/
    fo(i,1,m) write(ans[i]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值