【线段树】洛谷3582KIN

题目描述

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

输入输出格式

输入格式:

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

输出格式:

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

输入输出样例

输入样例#1:
9 4
2 3 1 1 4 1 2 4 1
5 3 6 6
输出样例#1:
15

说明

共有m部电影,编号为1~m,第i部电影的好看值为w[i]。

在n天之中(从1~n编号)每天会放映一部电影,第i天放映的是第f[i]部。

你可以选择l,r(1<=l<=r<=n),并观看第l,l+1,…,r天内所有的电影。如果同一部电影你观看多于一次,你会感到无聊,于是无法获得这部电影的好看值。所以你希望最大化观看且仅观看过一次的电影的好看值的总和。

思路

枚举左端点,用线段树维护最大值,每次ans=max(ans,sum[1])就行(sum是线段树数组)

第一遍过了,然后加了些常数优化,就到rank1了,500ms跑完。

细节在代码里解释。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
#include <set>
#include <map>
#include <vector>
#include <stack>
#include <list>
#define rep(i,m,n) for(register int i=m;i<=n;i++)
#define dop(i,m,n) for(register int i=m;i>=n;i--)
#define lowbit(x) (x&(-x))
#define ll long long
#define INF 2147483647
#define Open(s) freopen(s".in","r",stdin);freopen(s".out","w",stdout);
#define Close fclose(stdin);fclose(stdout);
#define re register
using namespace std;
inline int read(){
    int s=0,w=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
    return s*w;
}
const int maxn=1000010;
int a[maxn],b[maxn],Next[maxn],last[maxn],n,m,add[maxn<<2];
ll sum[maxn<<2],ans;
inline void PushDown(int now){
    if(add[now]){
      add[now<<1]+=add[now];
      add[now<<1|1]+=add[now];
      sum[now<<1]+=add[now];
      sum[now<<1|1]+=add[now];
      add[now]=0;
    }
}
inline void Change(int now,int l,int r,int wl,int wr,int p){     //线段树(RMQ)
    if(l>wr||r<wl) return;
    if(l>=wl&&r<=wr){sum[now]+=p;add[now]+=p;return;}
    PushDown(now);
    int mid=(l+r)>>1;
    Change(now<<1,l,mid,wl,wr,p);
    Change(now<<1|1,mid+1,r,wl,wr,p);
    sum[now]=max(sum[now<<1],sum[now<<1|1]);
}
int main(){
    n=read();m=read();
    rep(i,1,n) a[i]=read();
    rep(i,1,m) b[i]=read();
    dop(i,n,1) Next[i]=last[a[i]],last[a[i]]=i;    //Next[i]表示下一个放第a[i]部电影的时间,last[a[i]]表示上一个放第a[i]部电影的时间(循环执行完last[a[i]]表示第一个放第a[i]部电影的时间)
    rep(i,1,m)
       if(last[i])          //如果这部电影播放过
         if(!Next[last[i]]) Change(1,1,n,last[i],n,b[i]);   //如果没有第二次播放,把这次播放以后每个时间都加上这部电影的价值,因为这部电影播放后任何包括它的区间都能因它受益
         else Change(1,1,n,last[i],Next[last[i]]-1,b[i]);  //如果有第二次播放,把第一次播放和第二次播放之间的时间加上这部电影的价值,理由同上
    rep(i,1,n){   //枚举左端点
       ans=max(ans,sum[1]);   //更新最优解
       int t=Next[i];
       if(t){    //如果有第二次播放a[i]这部电影,
         Change(1,1,n,i,t-1,-b[a[i]]);      //先把这次和第二次播放之间减掉这部电影价值(同一部电影播放2次价值为0)
         if(Next[t]) Change(1,1,n,t,Next[t]-1,b[a[i]]);  //如果有第三次播放,把第二次和第三次之间的时间减掉这部电影价值
         else Change(1,1,n,t,n,b[a[i]]);    //否则把第二次播放之后的时间都加上这部电影价值
       }
       else Change(1,1,n,i,n,-b[a[i]]);  //如果没有第二次播放这部电影,把这部电影之后的时间都减掉这部电影价值
    }
    printf("%lld\n",ans);
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值