【链表】数据备份

题意
一条街上有n栋楼,你需要为k对楼连上电缆(2*k<=n),每对代价为两栋楼之间的距离。
每栋楼只能被配对一次,求配对最小代价。
【思路】
十分巧妙。
很明显一开始计算出每相邻两栋楼的距离差,然后从中选k个。
首先想到的应当是贪心,从小到大选,可问题在于不能选相邻两个。
其根本在于,我们选小的x,有可能同时选左边的l与右边的r可能获得更优解,所以不能使用贪心。
那么我们只需要给后面留一个反悔的机会,不选x而是选l与r就好了!
具体的方法是,设val[x]为x具体的值,选择x以后,将其从小顶堆中弹出,删除l与r,再加入一个值为val[l]+val[r]-val[x]的元素,如果这个值比较小被后面贪心选中,就相当于反悔不选x而选l与r了。
【代码】

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#define MAXN 110000
#define pa pair<int,int>
#define mp make_pair
#define inf 0x3f3f3f3f
using namespace std;
priority_queue< pa,vector<pa>,greater<pa> > list;
int n,m,a[MAXN],pre[MAXN],nex[MAXN];
int read()
{
    int ans=0,f=1,x;
    while(1)
    {
        x=getchar();
        if(x=='-')f=-1;
        if(x>='0'&&x<='9')break;
    }
    while(x>='0'&&x<='9')
    {
        ans=ans*10+x-'0';
        x=getchar();
    }
    return ans*f;
}
int main()
{
    n=read();m=read();

    int last=0;
    for(int i=1;i<=n;i++)
    {
        int x=read();
        a[i]=x-last;
        pre[i]=i-1;
        nex[i]=i+1;
        last=x;
    }
    pre[2]=0;
    nex[n]=0;

    for(int i=2;i<=n;i++)
        list.push(mp(a[i],i));  

    int ans=0;  
    for(int i=1;i<=m;i++)
    {

        while(list.top().first!=a[list.top().second]) list.pop();

        int val=list.top().first,x=list.top().second,l=pre[x],ll=pre[l],r=nex[x],rr=nex[r];
        ans+=val;
        list.pop();

        nex[ll]=x;

        pre[x]=ll;
        nex[x]=rr;

        pre[rr]=x;

        if(l&&r)a[x]=a[l]+a[r]-a[x];
        else a[x]=inf;
        a[l]=a[r]=inf;

        list.push(make_pair(a[x],x));
    }

    printf("%d\n",ans);

    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_20340417/article/details/80020700
今日推荐