【NOIP2016提高A组集训第12场11.10】灵知的太阳信仰

Description

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

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

Solution

这道题有一个很显然的dp方程,f[i]=f[j]+max(b[j+1…i])。
我们设后面的为g,那么很明显g是递增的。
那么用一个队列来维护后面的那个东西。
因为要满足一段里面元素不重复,所以要求一个l[i]表示i往左最远扩展到的节点。
那么每次,如果队首的位置小于l[i]那么head++,如果队尾的b小于等于当前的b[i],那么tail–(因为要像一个单调栈一样,有高的就把它踢掉)。(要保证当前这个队列里面所有的值都合法)。
那么现在i就可以进队了。
现在就有两种情况:
1、这个队列只有一个数,就是刚刚加进来的那个数,那么就直接更改一下队列中的值就好了。
2、这个队列中不止一个数,那么除了要更改刚刚进来的数的值,现在还要更新一下最小的值(用一个set来维护),现在的head因为l[i]不一样了,所以初始的高度可能会不一样,所以需要重新入队一次。

Code

#include
#include
#include
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=1e5+7;
multiset<int>t;
int i,j,k,n,m,ans,head,tail;
int data[maxn],a[maxn],b[maxn],hou[maxn],l[maxn];
int sum[maxn],wei[maxn],f[maxn];
int main(){
    freopen("array.in","r",stdin);
    freopen("array.out","w",stdout);
    scanf("%d",&n);
    fo(i,1,n)scanf("%d%d",&a[i],&b[i]);
    fo(i,1,n)l[i]=hou[a[i]]+1,hou[a[i]]=i;
    fo(i,1,n)l[i]=max(l[i-1],l[i]);
    head=1;
    fo(i,1,n){
        k=i-1;
        while(head1]while(head<=tail&&b[i]>data[tail]){
            t.erase(t.find(sum[tail]));
            k=wei[tail];
            tail--;
        }
        data[++tail]=b[i];
        wei[tail]=k;
        if(head!=tail){
            sum[tail]=f[wei[tail]]+data[tail];
            t.insert(sum[tail]);
            t.erase(t.find(sum[head]));
        }
        sum[head]=f[l[i]-1]+data[head];
        t.insert(sum[head]);
        f[i]=*t.begin();
    }
    printf("%d\n",f[n]);
}

你可能感兴趣的:(noip,DP,单调栈)