有趣的有趣的家庭菜园

题目描述

职业经营家庭菜园的JOI君每年在自家的田地中种植一种叫做IOI草的植物。IOI草的种子在冬天被播下,春天会发芽并生长至一个固定的高度。到了秋天,一些IOI草会结出美丽的果实,并被收获,其他的IOI草则会在冬天枯萎。
JOI君的田地沿东西方向被划分为N个区域,从西侧开始的第i个区域中种植着IOI草i。在第i个区域种植的IOI草,在春天的时候高度会生长至Hi,此后便不再生长。如果IOI草i会结出果实,那么将会获得Pi的收益,否则没有收益。
春天到了,查看田地样子的JOI君决定拔掉一些种植的IOI草,使利益最大化。拔掉IOI草i需要Ci的花销,拔掉的IOI草会立刻枯萎。IOI草只能在春天被拔掉,夏天和秋天不能拔掉IOI草。
IOI草是一种非常依靠阳光的植物,如果在夏天某个区域的IOI草的东侧和西侧都有比它高的IOI草存在,那么这株IOI草在秋天便不会结出果实。换句话说,为了让没有被拔掉的IOI草i在秋天结出果实,到了夏天的时候,以下两个条件至少满足一个:
1.对于任意1<=j<=i-1,Hj<=Hi或IOI草j已经被拔除
2.对于任意i+1<=j<=N,Hj<=Hi或IOI草j已经被拔除
用最终收获的果实的总价格减掉拔除IOI草的花销的总和,即为JOI君的收益。那么JOI君能从IOI草中获取的最大利益到底有多少呢?

DP

最后带来收益的草的高度会形成上凸函数图像。
于是一个显然的想法是设f[i]表示单调增部分,g[i]表示单调减部分,然后枚举顶点。
以f[i]为例,转移是
f[i]=max(f[j]-j+1~i-1所有比i高的草的拔除代价和)
其中要满足h[j]<=h[i]
我们设后面部分为a[h[i]]
那么每次i向右移,对于1~h[i]-1的草i这颗草都会遮挡阳光需要拔除,所以区间[1,h[i]-1]减去c[i]。
而对于h[i]~n,那么则是可以转移过来的,所以区间[h[i],n]对f[i]取max。
于是我们可以这样描述任务:
1、需要区间取max
2、需要区间加
那么要打add和mx标记,其中一个区间的add标记为b,mx标记为c,那么该区间的元素为a的值更改后为max(a,c)+b。
因此,给一个区间打add标记,直接与该区间原本的add标记合并。
那么给区间打mx标记呢?
我们想想当前add标记为b,mx标记为c,你要打一个mx标记为d。直接合并两个mx标记显然是错的。
思考一下当前a为max(a,c)+b
更改后应为max(max(a,c)+b,d)
那么就是max(max(a,c),d-b)+b
这样就可以进行mx标记合并了!

#include
#include
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef long long ll;
const ll maxn=100000+10;
ll h[maxn],c[maxn],b[maxn],p[maxn];
ll tree[maxn*5],add[maxn*5],mx[maxn*5];
bool bz[maxn*5];
ll f[maxn],g[maxn];
ll i,j,k,l,t,n,m,ans;
void mark(ll p,ll type,ll t){
    if (type==1) add[p]+=t,tree[p]+=t;
    else{
        tree[p]=max(tree[p],t);
        t-=add[p];
        if (bz[p]) mx[p]=max(mx[p],t);
        else{
            bz[p]=1;
            mx[p]=t;
        }
    }
}
void down(ll p){
    if (bz[p]){
        mark(p*2,0,mx[p]);
        mark(p*2+1,0,mx[p]);
        bz[p]=mx[p]=0;
    }
    if (add[p]){
        mark(p*2,1,add[p]);
        mark(p*2+1,1,add[p]);
        add[p]=0;
    }
}
void change(ll p,ll l,ll r,ll a,ll b,ll type,ll t){
    if (a>b) return;
    if (l==a&&r==b){
        mark(p,type,t);
        return;
    }
    down(p);
    ll mid=(l+r)/2;
    if (b<=mid) change(p*2,l,mid,a,b,type,t);
    else if (a>mid) change(p*2+1,mid+1,r,a,b,type,t);
    else change(p*2,l,mid,a,mid,type,t),change(p*2+1,mid+1,r,mid+1,b,type,t);
    tree[p]=max(tree[p*2],tree[p*2+1]);
}
ll query(ll p,ll l,ll r,ll a){
    if (l==r) return tree[p];
    down(p);
    ll mid=(l+r)/2;
    if (a<=mid) return query(p*2,l,mid,a);else return query(p*2+1,mid+1,r,a);
}
int main(){
    freopen("herbary.in","r",stdin);freopen("herbary.out","w",stdout);
    scanf("%lld",&n);
    fo(i,1,n) scanf("%lld%lld%lld",&h[i],&p[i],&c[i]),b[i]=h[i];
    sort(b+1,b+n+1);
    l=unique(b+1,b+n+1)-b-1;
    fo(i,1,n) h[i]=lower_bound(b+1,b+l+1,h[i])-b;
    fo(i,1,n){
        f[i]=query(1,1,n,h[i])+p[i];
        change(1,1,n,h[i],n,0,f[i]);
        change(1,1,n,1,h[i]-1,1,-c[i]);
    }
    fill(tree+1,tree+4*n+1,0);
    fill(bz+1,bz+4*n+1,0);
    fill(add+1,add+4*n+1,0);
    fill(mx+1,mx+4*n+1,0);
    fd(i,n,1){
        g[i]=query(1,1,n,h[i])+p[i];
        change(1,1,n,h[i],n,0,g[i]);
        change(1,1,n,1,h[i]-1,1,-c[i]);
    }
    ans=0;
    fo(i,1,n) ans=max(ans,f[i]+g[i]-p[i]);
    printf("%lld\n",ans);
}

你可能感兴趣的:(一般动规与递推,线段树)