车展

车展

Time Limits: 1000 ms Memory Limits: 65536 KB

Description
遥控车是在是太漂亮了,韵韵的好朋友都想来参观,所以游乐园决定举办m次车展。车库里共有n辆车,从左到右依次编号为1,2,…,n,每辆车都有一个展台。刚开始每个展台都有一个唯一的高度h[i]。主管已经列好一张单子:
L1 R1
L2 R2

Lm Rm
单子上的(Li,Ri)表示第i次车展将要展出编号从Li到Ri的车。
为了更加美观,展览时需要调整展台的高度,使参展所有展台的高度相等。展台的高度增加或减少1都需花费1秒时间。由于管理员只有一个人,所以只好对每个展台依次操作。每次展览结束后,展台高度自动恢复到初始高度。
请告诉管理员为了举办所有展览,他最少需要花多少时间将展台调整好。

Input
第一行为两个正整数n、m。
第二行共n个非负整数,表示第i辆车展台的高度h[i]。
接下来m行每行2个整数Li、Ri(Li≤Ri)。

Output
一个正整数,调整展台总用时的最小值。

Sample Input

6 4
4 1 2 13 0 9
1 5
2 6
3 4
2 2

Sample Output

48

Hint
【数据规模和约定】
对于50%的数据 n≤500,m≤1000;
对于80%的数据 n≤1000,m≤100000;
对于100%的数据n≤1000,m≤200000;
答案小于2^64。


题目大意

给出n个高低不同的展台,它可以升降,升一个单位或降一个单位需要1个花费,有给出你m个询问,第i个询问有一个范围[Ai,Bi],现在让你将区间[Ai,Bi]内的每个站台从原来的状况,升降到同一高度,得到一个单次询问花费Vi,求所有Vi的和的最小值。

解题思路

因为要得到最小花费,那么变化后的目的高度一定是这个区间的中位数。

我们可以先预处理答案,求出每个区间的最小花费总和,在这里,我们可以维护两个堆,第一个堆是小根堆,即小顶堆,存这个区间中比较大的那一半;另一个是大顶堆,存这个区间较小的一半,每次保证小顶堆当前的容量小于等于大顶堆的容量,且容量最多相差1,那么这个区间的中位数就是大顶堆的堆顶。

但我们如果再去一个个升或降的话,就需要O(n)一共O(n^3) <<Boom!!!>> <script type="math/tex" id="MathJax-Element-15">< ></script>,于是,我们观察一下计算结果的式子:

i=1n|Aix|
(x为中位数)

假如我们将A小到大排序一下,变成B,那么式子就变为

(xB1)+(xB2)+...+(xBmid)+(Bmid+1x)+...+(Bn1x)+(Bnx)

转换一下:

(j=[n2+1]nBj)(i=1[n2]Bi)

发现

当n是偶数时, ([n2]i=1Bi) 是小根堆的总和, (nj=[n2+1]Bj) 是大根堆的总和

当n是奇数时, ([n2]i=1Bi) 是小根堆的总和, (nj=[n2+1]Bj) 是大根堆的总和减去中位数

于是乎我们就可以在插入堆的同时维护两个值分别表示较大那个堆(小顶堆)的和,和较小那个堆(大顶堆)的和。

Codes:
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define fo(i,x,y) for(int i=x;i<=y;i++)
using namespace std;

int n,m;
long long tot=0,a[1001],maxx[1001],minn[1001],ans[1001][1001],amax,amin;

int getmax();//从大顶堆中取出
int getmin();//从小顶堆中取出
void putmax(int);//加入大顶堆
void putmin(int);//加入小顶堆
void upmax(int);
void upmin(int);
void downmax(int);
void downmin(int);

int main()
{
    scanf("%d%d",&n,&m);
    fo(i,1,n)scanf("%lld",&a[i]);
    fo(i,1,n-1)
    {
        memset(minn,0x7f,sizeof(minn));
        amin=minn[0]=maxx[0]=0;//初始化堆
        maxx[++maxx[0]]=a[i];/*amax和amin为各堆内元素和*/
        amax=a[i];
        fo(j,i+1,n)
        {
            if(maxx[0]==minn[0])//两堆容量相等,在最后使得大顶堆多一
            {
                if(a[j]<minn[1])/*元素要放入大顶堆*/
                {
                    putmax(a[j]);
                    amax+=a[j];
                }else{/*元素要放到小顶堆的时候,把小顶堆的堆顶移至大顶堆,存入元素*/
                    int x=getmin();
                    putmin(a[j]);
                    putmax(x);
                    amax+=x;
                    amin+=a[j]-x;
                }
            }else{/*两堆容量不等,在最后使得相等*/
                if(maxx[1]>a[j])
                {
                    int x=getmax();
                    putmax(a[j]);
                    putmin(x);
                    amin+=x;
                    amax+=a[j]-x;
                }else{
                    putmin(a[j]);
                    amin+=a[j];
                }
            }
            if((j-i)%2==0)
                ans[i][j]=ans[j][i]=amin-amax+maxx[1];
            else 
                ans[i][j]=ans[j][i]=amin-amax;/*求答案*/
        }
    }
    fo(i,1,m)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        tot+=ans[x][y];
    }
    printf("%lld",tot);
}

int getmax()
{
    int ans=maxx[1];
    maxx[1]=maxx[maxx[0]--];
    downmax(1);
    return ans;
}

int getmin()
{
    int ans=minn[1];
    minn[1]=minn[minn[0]--];
    downmin(1);
    return ans;
}

void putmax(int x)
{
    maxx[++maxx[0]]=x;
    upmax(maxx[0]);
}

void putmin(int x)
{
    minn[++minn[0]]=x;
    upmin(minn[0]);
}

void upmin(int x)
{
    if(x==1)return;
    if(minn[x]>=minn[x>>1])return;
    swap(minn[x>>1],minn[x]);
    upmin(x>>1);
}

void upmax(int x)
{
    if(x==1)return;
    if(maxx[x]<=maxx[x>>1])return;
    swap(maxx[x>>1],maxx[x]);
    upmax(x>>1);
}

void downmax(int x)
{
    if(x*2>maxx[0])return;
    int a,b=-1;
    a=maxx[x*2];
    if(x*2+1<=maxx[0])b=maxx[x*2+1];
    if(maxx[x]<a)
    {
        if(a<b)
        {
            swap(maxx[x],maxx[x*2+1]);
            downmax(x*2+1);
        }else{
            swap(maxx[x],maxx[x*2]);
            downmax(x*2);
        }
    }else if(maxx[x]<b)
    {
        swap(maxx[x],maxx[x*2+1]);
        downmax(x*2+1);
    }
}

void downmin(int x)
{
    if(x*2>minn[0])return;
    int a,b=0x7fffffff;
    a=minn[x*2];
    if(x*2+1<=minn[0])b=minn[x*2+1];
    if(minn[x]>a)
    {
        if(a>b)
        {
            swap(minn[x],minn[x*2+1]);
            downmin(x*2+1);
        }else{
            swap(minn[x],minn[x*2]);
            downmin(x*2);
        }
    }else if(minn[x]>b)
    {
        swap(minn[x],minn[x*2+1]);
        downmin(x*2+1);
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值