BZOJ3747 POI2015 Kinoman 【线段树】*

114 篇文章 0 订阅
55 篇文章 2 订阅

BZOJ3747 POI2015 Kinoman


Description

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

Input

第一行两个整数 n,m(1<=m<=n<=1000000) n , m ( 1 <= m <= n <= 1000000 )
第二行包含n个整数 f[1],f[2],,f[n](1<=f[i]<=m) f [ 1 ] , f [ 2 ] , … , f [ n ] ( 1 <= f [ i ] <= m )
第三行包含m个整数 w[1],w[2],,w[m](1<=w[j]<=1000000) w [ 1 ] , w [ 2 ] , … , w [ m ] ( 1 <= w [ j ] <= 1000000 )

Output

输出观看且仅观看过一次的电影的好看值的总和的最大值。

Sample Input

9 4
2 3 1 1 4 1 2 4 1
5 3 6 6

Sample Output

15
样例解释:
观看第2,3,4,5,6,7天内放映的电影,其中看且仅看过一次的电影的编号为2,3,4。


线段树可以维护的真的不只是线段,区间信息什么的,或者时间以及各种维度

只有你想不到没有线段树维护不了的

这道题就是线段树小技巧的运用,自己也是看了题解才做出来的

我们先考虑全局状态,假设当前我们知道一个区间 [l,r] [ l , r ] 的答案,那么我们考虑怎么把l向右移动一位,也就是在区间中减少一次 f[l] f [ l ] 的出现次数
如果我们对于一个l,同时维护它对应的所有的r的答案呢?
我们发现,答案的变化之和下一个 f[l] f [ l ] 的位置有关
所以我们可以预处理一下 nxt[l] n x t [ l ] 表示下一个颜色和l相同的位置
那么我们可以发现,删掉一个l之后 l nxt[l]1 l   n x t [ l ] − 1 的答案增加了 w[l] w [ l ]
nxt[l] nxt[nxt[l]] n x t [ l ]   n x t [ n x t [ l ] ] 的答案增加了 w[l] w [ l ]

当然还需要判断一下边界什么的

细节最好自己思考清楚


#include<bits/stdc++.h>
using namespace std;
#define LD (t<<1)
#define RD (t<<1|1)
#define N 1000010
#define LL long long
LL maxv[N<<2],add[N<<2];
int n,m,f[N],w[N],nxt[N],last[N],vl[N];
void pushdown(int t){
    if(add[t]){
        add[LD]+=add[t];maxv[LD]+=add[t];
        add[RD]+=add[t];maxv[RD]+=add[t];
        add[t]=0;
    }
}
void pushup(int t){maxv[t]=max(maxv[LD],maxv[RD]);}
void build(int t,int l,int r){
    if(l>=r)return;
    int mid=(l+r)>>1;
    build(LD,l,mid);
    build(RD,mid+1,r);
    pushup(t);
}
void modify(int t,int l,int r,int L,int R,int val){
    if(l>r)return;
    if(L<=l&&r<=R){add[t]+=val;maxv[t]+=val;return;}
    pushdown(t);
    int mid=(l+r)>>1;
    if(R<=mid)modify(LD,l,mid,L,R,val);
    else if(L>mid)modify(RD,mid+1,r,L,R,val);
    else{
        modify(LD,l,mid,L,mid,val);
        modify(RD,mid+1,r,mid+1,R,val);
    }
    pushup(t);
}
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--)nxt[i]=last[f[i]],last[f[i]]=i;
    for(int i=1;i<=m;i++)
        if(last[i]){
            if(!nxt[last[i]])modify(1,1,n,last[i],n,w[i]);
            else modify(1,1,n,last[i],nxt[last[i]]-1,w[i]);
        }
    LL ans=0;
    for(int i=1;i<=n;i++){
        ans=max(ans,maxv[1]);
        if(!nxt[i])modify(1,1,n,i,n,-w[f[i]]);
        else{
            modify(1,1,n,i,nxt[i]-1,-w[f[i]]);
            if(nxt[nxt[i]])modify(1,1,n,nxt[i],nxt[nxt[i]]-1,w[f[i]]);
            else modify(1,1,n,nxt[i],n,w[f[i]]);
        }
    }
    printf("%lld",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值