newcoder H肥猪(单调队列 / 线段树)题解

题意:

小B来到了一个异世界,成为了肥猪之王。
在这个异世界,共有n种肥猪,编号分别为1,...,n。
小B希望集齐这n种肥猪。
召集肥猪有两种方式:
1. 花费a[i]的金币召唤一只编号为i的肥猪。
2. 花费x的金币使所有已召集的肥猪进化。
即编号为i的肥猪编号变成i+1,特殊的,编号为n的肥猪编号变成1。

请问小B最少要花多少金币才能集齐n种肥猪。

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

思路(官方):

从0到n-1枚举第二种操作的使用次数step,那么对于最终得到的编号为i的肥猪,假如它是召唤编号为j的肥猪然后进化多次得到的,则一定有 istepjii−step≤j≤i ,并且这是充要的,即它可以由这个区间的任何一个j召唤后进化多次得到。因此只用这个区间的a[j]的最小值就是得到i的代价。把所有i的代价相加再加上step*x就是step对应的最小代价。注意,这个题目是一个环而不是链,这只需要将a复制一份即可。求区间最小值有很多方法,比如单调队列。时间复杂度 O(n2) 。

之前我自己想的是直接找生成每一个点的最小代价(a[k] + step * x),然后保存最大步长,但是这里有个问题,就是我这个step其实是公用的,按照我原来的思路用a[k] + step * x去找最小代价,那么其实是误判了最佳情况,所以wa了orz

单调队列好久没写了...凑活写了一下...

代码:

#include<set>
#include<map>
#include<stack>
#include<cmath>
#include<queue>
#include<string>
#include<cstdio>
#include<cstring>
#include<sstream>
#include<algorithm>
typedef long long ll;
using namespace std;
const int maxn = 2000 + 10;
const int MOD = 1e9 + 7;
const ll INF = 1e19;
ll a[maxn << 1], q[maxn << 1], pos[maxn << 1];  //递增队列
ll n, x;

int main(){
    scanf("%lld%lld", &n, &x);
    for(int i = 0; i < n; i++){
        scanf("%lld", &a[i]), a[n + i] = a[i];
    }
    ll ans = INF;
    for(int step = 0; step <= n - 1; step++){
        int head = 0, tail = 0;
        ll ret = 0;
        for(int i = n - step; i < n; i++){
            while(head < tail && q[tail - 1] >= a[i]) tail--;
            q[tail] = a[i];
            pos[tail++] = i;
        }
        for(int i = n; i < n + n; i++){
            while(head < tail && q[tail - 1] >= a[i]) tail--;
            q[tail] = a[i];
            pos[tail++] = i;
            while(head < tail && pos[tail - 1] - pos[head] > step) head++;
            ret += q[head];
        }
        ret = ret + step * x;
        ans = min(ans, ret);
    }
    printf("%lld\n", ans);
    return 0;
}

 

posted @ 2019-02-03 21:05  KirinSB  阅读(397)  评论(0编辑  收藏  举报