[CF639D]Bear and Contribution

题目

传送门

题解

我们需要对一种情况有意识:当 \(b\ge5c\) 时,我们可以直接做,这里不再赘述.

下面我们讨论的是 \(b< 5c\) 的一般情况.

首先,我们将 \(k\) 个数都变成的数称作集合点.

那么,我们应该可以意识到,这个所谓的集合点是 \(v[i]+k\),其中 \(0\le k\le 4,i\in [1,n]\).

那么,我们可以将所有的备选集合点(至多可能有 \(5n\) 个)先全部算出来,然后再用某种方法计算用最便宜的方法将 \(k\) 个数变成和集合点一样的花费,然后更新答案.

至于我们怎么算这个花费呢?这里有两种方法:

方法一

我们可以枚举集合点 \(goal[i]\),然后二分第 \(k\) 便宜的数变到集合点的花费是多少,在这个二分中,我们又需要一个二分找到数字的位置,这个算法的复杂度是 \(\mathcal O(n\log 1e13\log n)\),机房某大佬亲测过不了...

方法二

考虑我们方法一的缺点在哪里?集合点对于 \(5\) 的余数变动时,所有数走一步的方案显然会变化,这就会造成我们需要重新计算所有的数的最小花费,或者说我们需要多用一个 \(\log 1e13\) 来枚举最大的花费.

那么,我们可以将目标点通过 \(\bmod 5\) 的余数分成五组,余数相同的分成一组,这样,每个点到同一组的点的花费,在集合点改变时,我们唯一需要改变的就是这些数走 \(5\) 的步数,而不需要改变走 \(1\) 的步数.

代码实现时,我们可以维护一个优先队列与一个基础分 \(base\),在新点(假设其花费为 \(f\))加入时,它放入队列的的实际值为 \(f'=f-base\).

而这个队列,我们只需要一直维护它的 size\(k\) 即可,多了就弹出最差的,刚好为 \(k\) 就可以更新答案.

在集合点移动时,我们可以通过更改 \(base\) 使得 \(base'=base+\frac{\Delta goal}{5}b\) 来实现队列的整体增加,然后顺便找一下集合点移动之后,又可以新增多少点到队列中,再更新即可.

统计答案时,显然对于某个有 \(k\) 个元素的队列,它的花费为 \(base\times k+\sum Q\),但是优先队列不能访问元素,所以我们还要维护其中的元素和.

因为我们有 \(n\) 个点,每个点只会进队一次,出队一次,显然时间复杂度 \(\mathcal O(n\log n)\).

代码

#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;

#define rep(i,__l,__r) for(signed i=(__l),i##_end_=(__r);i<=i##_end_;++i)
#define fep(i,__l,__r) for(signed i=(__l),i##_end_=(__r);i>=i##_end_;--i)
#define erep(i,u) for(signed i=tail[u],v=e[i].to;i;i=e[i].nxt,v=e[i].to)
#define writc(a,b) fwrit(a),putchar(b)
#define mp(a,b) make_pair(a,b)
#define ft first
#define sd second
typedef long long LL;
typedef pair<int,int> pii;
typedef unsigned long long ull;
typedef unsigned uint;
#define Endl putchar('\n')
// #define int long long
// #define int unsigned
// #define int unsigned long long

#define cg (c=getchar())
template<class T>inline void read(T& x){
    char c;bool f=0;
    while(cg<'0'||'9'<c)f|=(c=='-');
    for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
    if(f)x=-x;
}
template<class T>inline T read(const T sample){
    T x=0;char c;bool f=0;
    while(cg<'0'||'9'<c)f|=(c=='-');
    for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
    return f?-x:x;
}
template<class T>void fwrit(const T x){//just short,int and long long
    if(x<0)return (void)(putchar('-'),fwrit(-x));
    if(x>9)fwrit(x/10);
    putchar(x%10^48);
}
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
inline int gcd(const int a,const int b){return b?gcd(b,a%b):a;}
inline void getInv(int inv[],const int lim,const int MOD){
    inv[0]=inv[1]=1;for(int i=2;i<=lim;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
}
inline LL mulMod(const LL a,const LL b,const LL mod){//long long multiplie_mod
    return ((a*b-(LL)((long double)a/mod*b+1e-8)*mod)%mod+mod)%mod;
}

const int MAXN=200000;
const LL INF=0x3f3f3f3f3f3f3f3fll;
const LL MAXCOST=40000000000000ll;

int n,k;
LL b,c,v[MAXN+5];

inline void Init(){
    n=read(1),k=read(1),b=read(1ll),c=read(1ll);
    rep(i,1,n)v[i]=read(1ll);
}

LL pre[MAXN+5];
inline void solve1(){
    sort(&v[1],&v[n+1]);
    rep(i,1,n)pre[i]=pre[i-1]+v[i];
    LL ans=INF;
    rep(i,k,n)ans=Min(ans,c*(v[i]*k-pre[i]+pre[i-k]));
    writc(ans,'\n');
}

//通过余数对 5n 个目标进行分类
int goal[5][MAXN+5],sz[5];
//goal[][] 的第二维只需要开 n 的大小就足够了, 没必要开 5n

inline void Makegoal(){
    v[n+1]=INF;//为了让最后一个没特殊情况
    int ind;
    rep(i,1,n){
        rep(j,0,4)if(v[i]+j<v[i+1]){//为了防止有重复元素
            ind=((v[i]+j)%5+5)%5;
            goal[ind][++sz[ind]]=v[i]+j;
        }else break;
    }//注意, 这个 goal[5] 是天生有序的, 因为我们先把 v 排了序, 那么放进去的时候也是有序的
    // rep(i,0,4){
    //     printf("Now i == %d :>",i);
    //     rep(j,1,sz[i])writc(goal[i][j],' ');Endl;
    // }
}

LL ans;

inline int calc(const int x,const int aim){
    int r1=(x%5+5)%5,r2=(aim%5+5)%5;
    LL ret=0,tmp=(r2+5-r1)%5;
    ret=c*tmp;
    ret+=b*((aim-x-tmp))/5*b;
    return ret;
}

priority_queue< LL,vector<LL>,less<LL> >Q;
LL sum;//记录队列中的元素和
LL base;
int pos;
//base : 这些数的基础分是多少
//pos  : 指向第一个还未放进队列的最小的数
inline LL Getans(const LL s,const LL bas){
    return bas*k+s;
}
inline void Solve(const int r){//传余数
    // printf("-----------------r == %d--------------------\n",r);
    sum=0;while(!Q.empty())Q.pop();
/*------------对于 goal[r][1] 进行特殊处理--------------*/
    // printf("this is init:\n");
    pos=1,base=0;
    LL ret;
    while(pos<=n && v[pos]<=goal[r][1]){
        // printf("Now pos == %d\n",pos);
        ret=calc(v[pos],goal[r][1]);
        sum+=ret;
        // printf("going to put %lld into Q\n",ret);
        Q.push(ret);
        ++pos;
        // printf("update pos -> %d, sum -> %lld\n",pos,sum);
    }
    while(Q.size()>k){
        sum-=Q.top();
        Q.pop();
    }
    if(Q.size()==k)ans=Min(ans,Getans(sum,base));
    rep(i,2,sz[r]){
        base+=(goal[r][i]-goal[r][i-1])/5*b;//更新 base
        while(pos<=n && v[pos]<=goal[r][i]){
            ret=calc(v[pos],goal[r][i])-base;
            sum+=ret;
            Q.push(ret);
            ++pos;
        }
        while(Q.size()>k){
            sum-=Q.top();
            Q.pop();
        }
        if(Q.size()==k)ans=Min(ans,Getans(sum,base));
    }
}

inline void solve2(){
    sort(&v[1],&v[n+1]);
    Makegoal();
    ans=INF;
    rep(i,0,4)Solve(i);//分余数对目标进行分类
    writc(ans,'\n');
}

signed main(){
    Init();
    if(b>=5*c)//这个 b 也太贵了= =
        return solve1(),0;
    solve2();
    return 0;
}
posted @ 2020-07-22 16:51  Arextre  阅读(143)  评论(0编辑  收藏  举报