题目描述
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$的代码:
1 // luogu-judger-enable-o2 2 #include<bits/stdc++.h> 3 #define il inline 4 #define ll long long 5 using namespace std; 6 const int N=10005; 7 ll n,k,f[N][5000],ans=-100000000; 8 int a[N]; 9 il ll gi(){ 10 ll a=0;char x=getchar();bool f=0; 11 while((x<'0'||x>'9')&&x!='-')x=getchar(); 12 if(x=='-')x=getchar(),f=1; 13 while(x>='0'&&x<='9')a=a*10+x-48,x=getchar(); 14 return f?-a:a; 15 } 16 int main(){ 17 n=gi(),k=gi(); 18 for(int i=1;i<=n;i++)a[i]=gi(); 19 for(int i=1;i<=n;i++) 20 for(int j=1;j<=k;j++)f[i][j]=-100000000; 21 f[1][1]=a[1]; 22 for(int i=2;i<=n;i++) 23 for(int j=1;j<=k&&j<=i;j++){ 24 if(i-2>0)f[i][j]=max(f[i-1][j],f[i-2][j-1]+a[i]); 25 else f[i][j]=f[i-1][j]; 26 ans=max(f[i][j],ans); 27 } 28 cout<<ans; 29 return 0; 30 }
2、再发一波正解代码:
1 // luogu-judger-enable-o2 2 #include<bits/stdc++.h> 3 #define il inline 4 #define ll long long 5 using namespace std; 6 const int N=500005; 7 int n,k; 8 ll ans,tot,a[N],l[N],r[N],pos; 9 struct node{ 10 int v,id; 11 bool operator < (const node a)const {return v<a.v;} 12 }tmp; 13 priority_queue<node>q; 14 bool vis[N]; 15 il ll gi(){ 16 ll a=0;char x=getchar();bool f=0; 17 while((x<'0'||x>'9')&&x!='-')x=getchar(); 18 if(x=='-')x=getchar(),f=1; 19 while(x>='0'&&x<='9')a=a*10+x-48,x=getchar(); 20 return f?-a:a; 21 } 22 int main(){ 23 n=gi(),k=gi(); 24 for(int i=1;i<=n;i++){ 25 tmp.v=gi(),tmp.id=i;q.push(tmp); 26 l[i]=i-1;r[i]=i+1;a[i]=tmp.v; 27 } 28 r[0]=1,l[n+1]=n; 29 while(k--){ 30 while(vis[q.top().id])q.pop(); 31 tmp=q.top();q.pop(); 32 if(tmp.v<0)break; 33 ans+=tmp.v;pos=tmp.id; 34 a[pos]=a[l[pos]]+a[r[pos]]-a[pos]; 35 tmp.v=a[pos]; 36 vis[l[pos]]=vis[r[pos]]=1; 37 l[pos]=l[l[pos]],r[l[pos]]=pos; 38 r[pos]=r[r[pos]],l[r[pos]]=pos; 39 q.push(tmp); 40 } 41 cout<<ans; 42 return 0; 43 }