小红的数组操作

链接:https://ac.nowcoder.com/acm/contest/60282/D
来源:牛客网

题目描述
请注意,本题和easy版本的唯一区别是x的数据范围没有x=1的限制。

小红拿到了一个数组,她可以进行若干次以下操作:
1.选择一个元素,花费p,使其加x。
1.选择一个元素,花费q,使其减y。

小红希望若干次操作后,数组的平均数是一个整数。你能帮小红求出最小的总代价吗?
输入描述:
第一行输入五个正整数

n,p,x,q,y,

n代表数组的大小,其余几个变量如题目描述所示。

第二行输入

n个正整数

a i ,代表数组的元素。1≤n≤1e5

1≤a i,x,y,p,q≤1e9

输出描述:
如果无解,请输出-1。
否则输出一个整数,代表最小的总代价。
示例1
输入

3 3 1 5 6
2 3 4

输出

0

示例2

输入

5 5 2 4 3
2 3 2 2 2
输出

8

思路:

题意很简单,就是给你一堆数,你可以执行两种操作,一是选择一个数加x,代价是p,另一个操作是选择一个数减y,代价是q。

现在想要使得所有的数的平均数是个整数,问花费的最小代价是多少。

首先可以想到的一点是,虽然是说对每个数有两种操作,实际上因为要改变的是总和的值,也就是在总和的基础上执行两种操作。

那再换句话说,既然改变的是总和的值,总和改变,平均数也随之改变,所以改变的就是平均数的值,那平均数不一定十是个整数,所以和平均数对应的是余数。也就是说,总和对应的是平均数的余数,平均数是整数,余数是0,不是整数,余数是其他数。而我们要求的,就是余数为0的最小代价。

ok,分析到这里,实际上分析的结果就是,我们要求的是总和除以n之后的余数为0的最小代价是多少,实际上我们只需要对初始状态的余数进行加和减的操作就行了,然后进行代价的更新。

分析到此,已经可以看出解题的眉目了吧,没错就是DP。

上面分析的就是每个状态的含义,下面分析下状态转移方程:

首先初始化每个余数的代价是最大值inf。然后是初始状态下的余数的代价设为0。

然后将当前的余数放入队列中,判断队列是否为空。不为空的话,分别执行加和减的操作。

加之后的代价如果比之前的的代价要小,则更新该余数的代价,并将该余数放入队列中进行下次的更新操作。减也是一样的道理。

当队列为空的时候,说明所有可以达到的余数值的代价都已经更新完了。

那么现在如果余数0的代价不是开始设置的最大值(inf)的话,说明在更新过程中更新过,那么就是答案。否则的话就输出-1。

代码:

#include<bits/stdc++.h>
using namespace std;

const int N = 2e5+10;
typedef long long ll;
const ll inf = 1e18;
int n;
ll p , x , q , y;
ll dis[N];

queue<int> an;
ll dij(int sum){
    for(int i = 0 ; i < n ; i++)
        dis[i] = inf;//初始化,每个余数对应的代价都是inf
    dis[sum] = 0;    //当前余数sum的dis设为0
    an.push(sum);    //将当前余数sum放入队列an中
    while(!an.empty()){
        int now = an.front();
        an.pop();
        int ne1 = ((now+x)%n+n)%n;
        int ne2 = ((now-y)%n+n)%n;
        if(dis[ne1] > dis[now]+p){
            dis[ne1] = dis[now] + p;
            an.push(ne1);
        }
        if(dis[ne2] > dis[now] + q){
            dis[ne2] = dis[now] + q;
            an.push(ne2);
        }
    }
    if(dis[0] == inf)
        return -1;
    else
        return dis[0];
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> p >> x >> q >> y;
    ll sum = 0;
    ll a;
    for(int i = 1 ; i <= n ; i++){
        cin >> a;
        sum += a;
    }
    sum %= n;                   //当前余数是多少
    cout << dij(sum) << endl;
    return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值