bzoj 2151 种树 —— 思路+链表

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2151

先都放进堆里取最大的,但选了一个就不能选它两边的,所以可能不是最优,要有“反悔”的措施;

可以取出一个后把它两边的位置 l,r 在链表中删除,然后再加入一个元素 a[x] = a[l] + a[r] - a[x],如果日后选了这个元素,就表示“反悔”了,当初不选 x 而是同时选了两边的(同时选比只选一个或不选更优);

而此时这个 x 的两边实际上是 l-1 和 r+1,因为其实选的是 l,r,这点用链表删除和查询就可以体现,更何况两边也可能被这样合并过;

再记一个 vis 表示这个位置从堆里取出时能不能选了,和链表的删除一致;

因为每次取出都会增加一个选择的位置,所以取出 m 次即可。

代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
int const xn=2e5+5;
int n,m,a[xn],pr[xn],nt[xn],ans;
bool vis[xn];
struct N{
  int v,id;
  bool operator < (const N &y) const
  {return v<y.v;}
};
priority_queue<N>q;
int rd()
{
  int ret=0,f=1; char ch=getchar();
  while(ch<'0'||ch>'9'){if(ch=='-')f=0; ch=getchar();}
  while(ch>='0'&&ch<='9')ret=(ret<<3)+(ret<<1)+ch-'0',ch=getchar();
  return f?ret:-ret;
}
void del(int x){nt[pr[x]]=nt[x]; pr[nt[x]]=pr[x];}
int main()
{
  n=rd(); m=rd();
  for(int i=1;i<=n;i++)
    {
      a[i]=rd();
      q.push((N){a[i],i});
    }
  if(n<2*m){puts("Error!"); return 0;}
  for(int i=2;i<n;i++)pr[i]=i-1,nt[i]=i+1;
  pr[1]=n; nt[1]=2; pr[n]=n-1; nt[n]=1;
  for(int i=1;i<=m;i++)
    {
      int x=q.top().id,w=q.top().v; q.pop();
      while(vis[x])x=q.top().id,w=q.top().v,q.pop();
      int l=pr[x],r=nt[x]; ans+=w;
      del(l); del(r); vis[l]=1; vis[r]=1; a[x]=a[l]+a[r]-w;
      q.push((N){a[x],x});
    }
  printf("%d\n",ans);
  return 0;
}

 

posted @ 2018-10-19 19:19  Zinn  阅读(171)  评论(0编辑  收藏  举报