链表与邻接表

AcWing 136.邻值查找
这是我写链表的第一道练习题……
本题的思路很好想,就是对于每一个a[i]寻找最小的a[j]的过程进行优化。我们不妨这样考虑:将数组a排序后,每一个a[i]的邻值都是a[i-1]与a[i+1],但其前提是:它们对应着的原数组的下标必须小于a[i]对应着的原数组的下标。那么如何解决这个问题呢?我们可以建立一个有序链表,先找到原下标为n的元素,找到其符合条件的a[j]并将这个原下标为n的元素删除。这样枚举后续的元素时,一定不会挑中比自身原下标还要大的数。这个算法的瓶颈在于排序,时间复杂度为O(nlogn)。

#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=1e5+5;
typedef long long ll;
struct Node{
    ll sz,bh;
}a[N],ans[N];
int p[N],l[N],r[N];
bool cmp(Node x,Node y){
    return x.sz<y.sz;
}
int main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i].sz;
        a[i].bh=i;
    }
    sort(a+1,a+n+1,cmp);
    a[0].sz=3e9,a[n+1].sz=-3e9;
    for(int i=1;i<=n;i++){
        p[a[i].bh]=i;
        l[i]=i-1,r[i]=i+1;
    }
    for(int i=n;i>1;i--){
        int j=p[i];
        ll lv=abs(a[j].sz-a[l[j]].sz);
        ll rv=abs(a[j].sz-a[r[j]].sz);
        if(lv<rv) ans[i]={lv,a[l[j]].bh};
        else if(lv>rv) ans[i]={rv,a[r[j]].bh};
        else ans[i]={rv,a[l[j]].bh};
        r[l[j]]=r[j];
        l[r[j]]=l[j];
    }
    for(int i=2;i<=n;i++) cout<<ans[i].sz<<' '<<ans[i].bh<<endl;
    return 0;
}

AcWing 147.数据备份(链表+二叉堆)
这题没有模板,全是思维。
拿到这个题目的第一步就是分析与转换:通过简单的看图分析可以得出:我们只会选择两个相邻的办公楼相连。那么这个问题就转化为了:从一个序列(原数组的差分数组)中挑出k个互不相邻的数,使他们的和最小(第一步还真让我全部想到了)。
但是真正的难点在于如何解决转化后的问题。我们可以从贪心思想上进一步考虑:如果k=2,那么是否一定要选到最小值呢?假设最小值为D[i](D为差分数组),那么假如不选D[i-1]与D[i+1],那么我们就要选择D[i]与D[j](min(D[k]),且k≠i-1,i+1),必然选择最小值与除了D[i-1]与D[i+1]之外的次小值。那么假如选择D[i-1]与D[i+1]中的其中一个,那么D[i]就不能被选,显然这种情况不如上面那一种情况优。所以我们得出结论:D[i-1]与D[i+1]要么同时都不被选,要么同时被选。所以当k=2时,答案为min(D[i]+D[j],D[i-1]+D[i+1])。接下来的思路更加nb:由k=2推广到k>2,这是从特殊到一般。在计算机算法里,一般考虑继续采用k=2的算法,但是要将问题的规模缩小,变成原问题的子问题,到最后k=2时直接解决。这是一种常用的思路(递归、递推、分治、动规都用到了这一种缩小问题规模的思路)。那么我们就有了基本的思路:每次都在数组中做k=2时的算法,且要做到每一次都减少2个数(因为已经求出了两个数的和)。那么算法设计如下:初始化:建立一个链表,将差分数组中所有的数按顺序串起来。(设点p的左边节点为l[p],右边节点为r[p])每次对于最小值D[p],我们将其取出后,把它的值更新为D[l[p]]+D[r[p]]-D[p],并将答案加上D[p],删去l[p]与r[p]两个节点(这个方法真是妙不可言,真的需要经验的积累与灵感的爆发)。那么假如我们第二次还选p节点,答案就会被更新成D[l[p]]+D[r[p]],不会遗漏最优解。且删去l[p]与r[p]后不会再选到相邻的节点。
代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
const int N=2e5+5;
typedef long long ll;
ll a[N],d[N];
int l[N],r[N],idx;
bool st[N];
typedef pair<ll,int> PII;
priority_queue<PII,vector<PII>,greater<PII>> heap;
void remove(int p){
    st[p]=true;
    l[r[p]]=l[p];
    r[l[p]]=r[p];
}
int main(){
    int n,k;
    idx=n-1;
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
    }
    d[0]=d[n]=1e9;
    for(int i=0;i<=n;i++){
        l[i]=i-1;
        r[i]=i+1;
        if(i>=1 && i<n){
            d[i]=a[i+1]-a[i];
            heap.push({d[i],i});
        }
    }
    ll ans=0;
    while(k--){
        while(st[heap.top().second]) heap.pop();
        PII t=heap.top();
        int p=t.second;
        heap.pop();
        ans+=t.first;
        d[p]=d[l[p]]+d[r[p]]-d[p];
        remove(l[p]);remove(r[p]);
        heap.push({d[p],p});
    }
    cout<<ans<<endl;
    return 0;
}

拓展:
AcWing 163.生日礼物

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值