2017年10月23日提高组T2 灵知的太阳信仰 单调队列优化dp

123 篇文章 0 订阅
11 篇文章 0 订阅

Description


在炽热的核熔炉中,居住着一位少女,名为灵乌路空。
据说,从来没有人敢踏入过那个熔炉,因为人们畏缩于空所持有的力量——核能。
核焰,可融真金。

咳咳。
每次核融的时候,空都会选取一些原子,排成一列。然后,她会将原子序列分成一些段,并将每段进行一次核融。
一个原子有两个属性:质子数和中子数。
每一段需要满足以下条件:
1、同种元素会发生相互排斥,因此,同一段中不能存在两个质子数相同的原子。
2、核融时,空需要对一段原子加以防护,防护罩的数值等于这段中最大的中子数。换句话说,如果这段原子的中子数最大为x,那么空需要付出x的代价建立防护罩。求核融整个原子序列的最小代价和。

Input


第一行一个正整数N,表示原子的个数。
接下来N行,每行两个正整数pi和ni,表示第i个原子的质子数和中子数。

Output


输出一行一个整数,表示最小代价和。

Hint


Data Constraint
对于20%的数据,1<=n<=100
对于40%的数据,1<=n<=1000
对于100%的数据,1<=n<=10^5,1<=pi<=n,1<=ni<=2*10^4

Solution


题目一股中二气息

这里可以看出是要把原子分段,我们考虑dp设f[i]为前i项都核融的最小代价,那么f[i]=min{f[j]+mx(j+1,i)}满足(last[i]<=j < i),这里的last[i]是预处理的由i向左拓展的最远距离(也就是不含同类原子的最远距离),会T

观察一下可以发现当右端点i固定左端点j向左扫时其间b的最大值mx是不减的,这就相当于在找到下一个更大值之前mx不会变。这里维护一个递减的单调队列表示从i向左扫到那些位置会使mx发生变化。由于f是单调不减的,在mx不变时用最左边的f一定是最小值,这就相当于在单调队列中的每一项的f加上下一位的mx

由于某些神奇的原因这题暴力修改单调队列中的元素不会T,就放上来了

Code


#include <stdio.h>
#include <math.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <vector>
#define rep(i, st, ed) for (int i = st; i <= ed; i += 1)
#define drp(i, st, ed) for (int i = st; i >= ed; i -= 1)
#define erg(i, st) for (int i = ls[st]; i; i = e[i].next)
#define fill(x, t) memset(x, t, sizeof(x))
#define max(x, y) ((x)>(y)?(x):(y))
#define min(x, y) ((x)<(y)?(x):(y))
#define abs(x) ((x)<(0)?(-(x)):(x))
#define ll long long
#define ld lont double
#define db double
#define INF 0x3f3f3f3f
#define N 1000001
#define E 10001
int queue[N], last[N], a[N], b[N], f[N], pos[N];
inline int read() {
    int x = 0, v = 1;
    char ch = getchar();
    for (; ch < '0' || ch > '9'; v *= ((ch == '-')?(-1):(1)), ch = getchar());
    for (; ch <= '9' && ch >= '0'; (x *= 10) += ch - '0', ch = getchar());
    return x * v;
}
int main(void) {
    // freopen("data.in","r",stdin);
    // freopen("myp.out","w",stdout);
    int n = read();
    int head = 0;
    int tail = -1;
    rep(i, 1, n) {
        a[i] = read();
        b[i] = read();
        if (!pos[a[i]]) {
            pos[a[i]] = i;
            last[i] = 0;
        } else {
            last[i] = pos[a[i]];
            pos[a[i]] = i;
        }
        last[i] = max(last[i], last[i - 1]);
    }
    queue[++ tail] = 1;
    f[1] = b[1];
    rep(i, 2, n) {
        while (last[i] > queue[head] && head <= tail) {
            head += 1;
        }
        while (b[queue[tail]] <= b[i] && head <= tail) {
            tail -= 1;
        }
        queue[++ tail] = i;
        f[i] = INF;
        rep(j, head + 1, tail) {
            f[i] = min(f[queue[j - 1]] + b[queue[j]], f[i]);
        }
        f[i] = min(f[last[i]] + b[queue[head]], f[i]);
    }
    printf("%d\n", f[n]);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值