[P1484] 种树
Link:https://www.luogu.org/problemnew/show/P1484
Brief Introduction:在一个长为N上的序列取至多K个数,要保证所取的数两两不相邻,求Max(所取的数的和)。
N<=5e5,K<=N/2
Algorithm:
1、首先O(NK)的dp是能立刻想到的 dp[n][k]=max(dp[n-1][k],dp[n-2][k-1]+value[n])
可以使用滚动数组优化
但明显不足以解决N<=5e5的问题
2、可以从最简单的问题开始考虑,如果K=1,那么取最大值。设该点的位置为P。
当问题扩大时,棘手的问题在于不易维护所取数两两不相邻这一条件
为了每次选取最大值时可以忽略这个条件,我们选择P后将P-1,P,P+1合并,看为同一个点,这样保证接下来可以任意选点。
为了提供“反悔”这个选项,新点的权值设为value(P-1)+value(P+1)-value(P),如选择新点则表示“反悔”不选P,而改选P-1和P+1.
维护数列中插入、删除、求最大值,可以想到使用堆/Priority_queue维护
Code:
#include <bits/stdc++.h> using namespace std; inline int read() { char ch;int f=0,num; while(!isdigit(ch=getchar())) f|=(ch=='-'); num=ch-'0'; while(isdigit(ch=getchar())) num=num*10+ch-'0'; return f?-num:num; } #define F first #define S second const int MAXN=5e5+10; typedef pair<int,int> P; typedef long long ll; priority_queue<P> Q; bool vis[MAXN]; ll l[MAXN],r[MAXN],dat[MAXN]; int main() { int n=read(),k=read(); for(int i=1;i<=n;i++) { dat[i]=read(); l[i]=i-1;r[i]=i+1; //维护每个点的两边的点的坐标 Q.push(P(dat[i],i)); } ll res=0;l[0]=1;r[n+1]=n; //对边界设置 for(int i=1;i<=k;i++) { while(vis[Q.top().S]) Q.pop(); //如已被合并,则不处理 P t=Q.top();Q.pop(); if(t.F<0) break; res+=t.F;vis[l[t.S]]=vis[r[t.S]]=true; dat[t.S]=t.F=dat[l[t.S]]+dat[r[t.S]]-dat[t.S]; Q.push(t); r[t.S]=r[r[t.S]];l[t.S]=l[l[t.S]]; //不真正合并,仅维护左右点坐标 l[r[t.S]]=t.S;r[l[t.S]]=t.S; } cout << res; return 0; }
Review:
1、当遇到常见问题被加以特殊条件时,注意化归,通过转化将问题变为易解问题
能否忽略特殊条件解题
2、在决策类问题中,我们可以利用贪心+“反悔”选项的方式解决问题
将权值变为 其他解-当前贪心解
3、有时在维护合并操作时,不一定要真正合并。
可将信息集中在其中一点上,只维护每个点相邻点坐标即可。
同时,要对“废点”打上标记,之后对其不再处理。