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; }