P1484 种树
题目描述
cyrcyr今天在种树,他在一条直线上挖了n个坑。这n个坑都可以种树,但为了保证每一棵树都有充足的养料,cyrcyr不会在相邻的两个坑中种树。而且由于cyrcyr的树种不够,他至多会种k棵树。假设cyrcyr有某种神能力,能预知自己在某个坑种树的获利会是多少(可能为负),请你帮助他计算出他的最大获利。
输入输出格式
输入格式:第一行,两个正整数n,k。
第二行,n个正整数,第i个数表示在直线上从左往右数第i个坑种树的获利。
输出格式:输出1个数,表示cyrcyr种树的最大获利。
输入输出样例
6 3
100 1 -1 100 1 -1
200
说明
对于20%的数据,n<=20。
对于50%的数据,n<=6000。
对于100%的数据,n<=500000,k<=n/2,在一个地方种树获利的绝对值在1000000以内。
Solution:
本题的二叉堆的做法实在是太巧妙了!!!
首先,我打了一个普通的$DP$,期望得分$50$,很容易想到状态$f[i][j],\;i\in[1,n],\;j\in[1,k]$表示前$i$个位置中了$j$棵树的最大获利,则由题目限制条件不难想到状态转移方程:$f[i][j]=max(f[i-1][j],f[i-2][j-1]+a[i])$,注意下边界和初始状态就有$50$分了。时间空间复杂度都是$O(n^2)$,显然不行。
此时想了各种奇技淫巧,依然没用。。。还是默默的看了下题解,惊叹于本题的二叉堆做法:
我们先进行小规模枚举:$k=2$时,则有两种可能:1、另取一个与$a[i]$不相邻的$a[j]$。2、取$a[i-1]$和$a[i+1]$。
我们可以发现:如果$k=1$时最优解为$a[i]$,那么我们便可以把$a[i-1]$和$a[i+1]$进行合并,因为它们要么同时被选,要么同时落选(证明不难,请自行解决)。而且,我们还注意到:当选了$a[i-1]$和$a[i+1]$时,获利便增加了$a[i-1]+a[i+1]-a[i]$。所以当$a[i]$被选时,我们就可以删去$a[i-1]$和$a[i+1]$,并把$a[i]$改成$a[i-1]+a[i+1]-a[i]$(即使为负也没问题,因为下次不会选它,而若为正则等同于选了$a[i-1]$和$a[i+1]$),重新找最大的。
每次找的都是最大的数,我们便可以使用堆进行操作,直到堆中最大值小于等于$0$或取出$k$个数后停止。复杂度$O(nlogn)$。
1、先安利一下自己$DP$的代码:
#include<bits/stdc++.h> #define il inline #define ll long long using namespace std; const int N=10005; ll n,k,f[N][5000],ans=-100000000; int a[N]; il ll gi(){ ll a=0;char x=getchar();bool f=0; while((x<'0'||x>'9')&&x!='-')x=getchar(); if(x=='-')x=getchar(),f=1; while(x>='0'&&x<='9')a=a*10+x-48,x=getchar(); return f?-a:a; } int main(){ n=gi(),k=gi(); for(int i=1;i<=n;i++)a[i]=gi(); for(int i=1;i<=n;i++) for(int j=1;j<=k;j++)f[i][j]=-100000000; f[1][1]=a[1]; for(int i=2;i<=n;i++) for(int j=1;j<=k&&j<=i;j++){ if(i-2>0)f[i][j]=max(f[i-1][j],f[i-2][j-1]+a[i]); else f[i][j]=f[i-1][j]; ans=max(f[i][j],ans); } cout<<ans; return 0; }
2、再发一波正解代码:
#include<bits/stdc++.h> #define il inline #define ll long long using namespace std; const int N=500005; int n,k; ll ans,tot,a[N],l[N],r[N],pos; struct node{ int v,id; bool operator < (const node a)const {return v<a.v;} }tmp; priority_queue<node>q; bool vis[N]; il ll gi(){ ll a=0;char x=getchar();bool f=0; while((x<'0'||x>'9')&&x!='-')x=getchar(); if(x=='-')x=getchar(),f=1; while(x>='0'&&x<='9')a=a*10+x-48,x=getchar(); return f?-a:a; } int main(){ n=gi(),k=gi(); for(int i=1;i<=n;i++){ tmp.v=gi(),tmp.id=i;q.push(tmp); l[i]=i-1;r[i]=i+1;a[i]=tmp.v; } r[0]=1,l[n+1]=n; while(k--){ while(vis[q.top().id])q.pop(); tmp=q.top();q.pop(); if(tmp.v<0)break; ans+=tmp.v;pos=tmp.id; a[pos]=a[l[pos]]+a[r[pos]]-a[pos]; tmp.v=a[pos]; vis[l[pos]]=vis[r[pos]]=1; l[pos]=l[l[pos]],r[l[pos]]=pos; r[pos]=r[r[pos]],l[r[pos]]=pos; q.push(tmp); } cout<<ans; return 0; }