[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、有时在维护合并操作时,不一定要真正合并。

      可将信息集中在其中一点上,只维护每个点相邻点坐标即可。

      同时,要对“废点”打上标记,之后对其不再处理

猜你喜欢

转载自www.cnblogs.com/newera/p/8977924.html