[POI 2012]Kinoman(线段树)

题目链接

http://main.edu.pl/en/archive/oi/22/kin

题目大意

共有m部电影,编号为1~m,第i部电影的好看值为w[i]。
在n天之中(从1~n编号)每天会放映一部电影,第i天放映的是第f[i]部。
你可以选择l,r(1<=l<=r<=n),并观看第l,l+1,…,r天内所有的电影。如果同一部电影你观看多于一次,你会感到无聊,于是无法获得这部电影的好看值。所以你希望最大化观看且仅观看过一次的电影的好看值的总和。

思路

我们可以维护一个长度为 n 的线段树,初始时,线段树中第i位保存的是第 [1,i] 天看电影的好看值。
为了得到这个初始的线段树,我们可以预处理出每个电影第一次放映的时间 last[i] ,以及第 x 天看的电影的下一次放映时间next[x],然后我们枚举每个电影,假如这个电影全程只在第 x 天放映过一次,那么显然[1,x],[1,x+1]...[1,n]这些好看值里都会包含一个 w[f[x]] ,而如果这个电影放映过多次,第一次是在第 x 天放映,第二次是next[x]天放映,那么 [1,x]...[1,next[x]] 这些好看值里都会包含一个 w[f[x]]

然后我们从第2天到第 n 天枚举时间t,在这个线段树的基础上维护第 [t,i] 天看电影的好看值。显然,从 [t1,i] 转变为 [t,i] [t,t]...[t,next[t1]1] 的好看值里就没有了 w[f[t]] (这些区间原本包含1个电影f[t-1],现在不包含了), [t,next[t1]]...[t,next[next[t1]]1] 的好看值里会新添加入 w[f[x]] (这些区间原本包含2个电影f[t-1],现在只包含了一个),每次维护完之后,用整个线段树的最大值去更新答案即可。

于是我们只需要维护一个能支持区间修改、区间求最值的线段树即可

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 1100000

using namespace std;

typedef long long int LL;

LL addtag[MAXN<<2],maxtag[MAXN<<2],ans=0;

void pushdown(int o)
{
    addtag[o<<1]+=addtag[o];
    addtag[o<<1|1]+=addtag[o];
    maxtag[o<<1]+=addtag[o];
    maxtag[o<<1|1]+=addtag[o];
    addtag[o]=0;
}

void pushup(int o)
{
    maxtag[o]=max(maxtag[o<<1],maxtag[o<<1|1]);
}

void add(int o,int L,int R,int ql,int qr,LL val)
{
    if(ql<=L&&R<=qr)
    {
        addtag[o]+=val;
        maxtag[o]+=val;
        return;
    }
    int M=(L+R)>>1;
    pushdown(o);
    if(ql<=M) add(o<<1,L,M,ql,qr,val);
    if(qr>M) add(o<<1|1,M+1,R,ql,qr,val);
    pushup(o);
}

LL query(int o,int L,int R,int ql,int qr)
{
    if(ql<=L&&R<=qr)
        return maxtag[o];
    pushdown(o);
    int M=(L+R)>>1;
    LL ans=0;
    if(ql<=M) ans=max(ans,query(o<<1,L,M,ql,qr));
    if(qr>M) ans=max(ans,query(o<<1|1,M+1,R,ql,qr));
    return ans;
}

int n,m,f[MAXN],w[MAXN],last[MAXN],next[MAXN]; //next[i]=第i天放映的电影上一次什么时候放,last[i]=第i部电影上一次是什么时候放
//或者说last[i]=第i部电影第一次放映的时间

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&f[i]);
    for(int i=1;i<=m;i++) scanf("%d",&w[i]);
    for(int i=n;i>=1;i--)
    {
        next[i]=last[f[i]];
        last[f[i]]=i;
    }
    for(int i=1;i<=m;i++)
        if(last[i]) //第i个电影有放映过
        {
            if(!next[last[i]]) //第i个电影只放映过一次
                add(1,1,n,last[i],n,w[i]); //last[i]~n天为右端点的区间的最大权值和都要加上w[i]
            else //第i个电影放映过多次
                add(1,1,n,last[i],next[last[i]]-1,w[i]); //last[i]~next[last[i]]天为右端点d区间的最大权值和都会包含w[i]
        }
    for(int i=1;i<=n;i++) //当前线段树的第x个位置的值是[i,x]这段时间观看的得分
    {
        ans=max(ans,query(1,1,n,1,n));
        //然后从[i,x]这段的得分转变为[i+1,x]这段的得分
        int t=next[i];
        if(t)
        {
            add(1,1,n,i,t-1,-w[f[i]]);
            if(next[t]) add(1,1,n,t,next[t]-1,w[f[i]]);
            else add(1,1,n,t,n,w[f[i]]);
        }
        else add(1,1,n,i,n,-w[f[i]]);
    }
    printf("%lld\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值