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

44 篇文章 0 订阅
32 篇文章 0 订阅

题目

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

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

Input
第一行一个正整数N,表示原子的个数。
接下来N行,每行两个正整数pi和ni,表示第i个原子的质子数和中子数。
Output
输出一行一个整数,表示最小代价和。

Sample Input
5
3 11
2 13
1 12
2 9
3 13
Sample Output
26

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

大概意思就是说把一些数分成若干段,每一段中的a[i]两两不能相同,代价为 max(b[i]) ,求最小总代价。

20%

暴力乱搞。。。

40%

DP,设F[i]表示当前段结尾为i的最小代价和。
则可以从当前F[i]推向F[j],中途统计一下最大值再转移。

80%

用一个单调队列来存可能的每个转移位置,把其中位置靠前而且答案更不优的位置删除。
好吧这其实是水法但是我没打2333

100%

DP设F[i]表示当前段结尾为i的最小代价和(和40%一样)。
其中状态转移方程为
F[i]=F[j]+max(b[j+1]...b[i])(j=last...i1)
其中last表示最远的转移位置。
很显然从i往前的max(b[j+1]…b[i])一定是单调不降的。

这里写图片描述
阶梯表示max(b[j+1]…b[i])

每个圆圈的影响范围就是
这里写图片描述
(因为每个圈不会影响到自己,但是会影响到下一个圈)

所以可以用线段树维护F[i]+b[i]的最小值和F[i]的值,再用栈维护阶梯。

大致流程

比如要加入i+1个位置
这里写图片描述

那么就这样修改max(b[j])的值
这里写图片描述

然后统计min(F[j]+b[j])
这里写图片描述

再在线段树上修改F[i]的值
这里写图片描述

还有last的求法就自己脑补了啊2333

code

#include <iostream>
#include <cstdio>
#include <cstring>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
#define Max_len 524288
using namespace std;
int a[100001];
int b[100001];
int f[100001];
int tr[Max_len][3];
int d[100001];
int num[100001];
int ls[100001];
int n,i,j,t,l,sum;

int max(int x,int y)
{
    if (x>y)
    return x;
    else
    return y;
}

int min(int x,int y)
{
    if (x<y)
    return x;
    else
    return y;
}

void down(int t)
{
    if (tr[t][2]!=(-1))
    {
        tr[t*2][2]=tr[t][2];
        tr[t*2+1][2]=tr[t][2];

        tr[t][1]=tr[t][0]+tr[t][2];
        tr[t][2]=-1;
    }
}

void change0(int t,int l,int r,int id,int s)
{
    if (l==r)
    {
        tr[t][0]=s;
        return;
    }

    down(t*2);
    down(t*2+1);

    int mid=(l+r)/2;

    if (id<=mid)
    change0(t*2,l,mid,id,s);
    else
    change0(t*2+1,mid+1,r,id,s);

    tr[t][0]=min(tr[t*2][0],tr[t*2+1][0]);
}

void change1(int t,int l,int r,int x,int y,int s)
{
    if (y<x)
    return;

    if (l<r)
    {
        down(t*2);
        down(t*2+1);
    }

    if ((x<=l) && (r<=y))
    {
        tr[t][2]=s;
        down(t);

        return;
    }

    int mid=(l+r)/2;

    if (x<=mid)
    change1(t*2,l,mid,x,y,s);
    if (mid<y)
    change1(t*2+1,mid+1,r,x,y,s);

    tr[t][1]=min(tr[t*2][1],tr[t*2+1][1]);
}

void find(int t,int l,int r,int x,int y)
{
    if (y<x)
    return;

    if (l<r)
    {
        down(t*2);
        down(t*2+1);
        tr[t][1]=min(tr[t*2][1],tr[t*2+1][1]);
    }

    if ((x<=l) && (r<=y))
    {
        sum=min(sum,tr[t][1]);

        return;
    }

    int mid=(l+r)/2;

    if (x<=mid)
    find(t*2,l,mid,x,y);
    if (mid<y)
    find(t*2+1,mid+1,r,x,y);
}

int main()
{  
    freopen("array.in","r",stdin);
    freopen("array.out","w",stdout);

    fo(i,1,Max_len-1)
    tr[i][0]=2000000000,tr[i][1]=2000000000;

    scanf("%d",&n);
    fo(i,1,n)
    scanf("%d%d",&a[i],&b[i]),ls[i]=num[a[i]],num[a[i]]=i;

    f[1]=b[1];
    t=1;
    d[0]=0;
    d[1]=1;
    l=0;
    change0(1,0,n,0,0);
    change0(1,0,n,1,f[1]);
    change1(1,0,n,0,1,b[1]);

    fo(i,2,n)
    {
        l=max(l,ls[i]);//维护last

        while ((t>0) && (b[d[t]]<=b[i]) && (d[t]>=l))
        t--;

        change1(1,0,n,d[t],i-1,b[i]);
        d[++t]=i;

        sum=233333333;
        find(1,0,n,l,i-1);

        f[i]=sum;

        change0(1,0,n,i,f[i]);
    }

    printf("%d\n",f[n]);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值