题目描述

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

输入输出格式

输入格式:

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

输出格式:

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

输入输出样例

输入样例#1:

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

输出样例#1:

15

思路:这道题目我们可以考虑先记录每种电影上一次开播时间和下一次开播时间(即以下代码中的\(last\)数组和\(nxt\)数组),然后对于每种电影,我们可以先处理中它是否播放过对后面区间的影响情况,然后再对\(n\)个时间点分别考虑,我们可以枚举左端点,然后根据左端点电影的播放情况就可以确定它可以影响到的最右端点,然后不断更新,更新过程中记录最大值,最后那个最大值即为答案。

洛谷P3582(自己写的题解)

代码:

#include<cstdio>
#include<algorithm>
#include<cctype>
#define ll long long
#define maxn 1000007
#define ls rt<<1
#define rs rt<<1|1
using namespace std;
int n,m,f[maxn],nxt[maxn],last[maxn],a[maxn];
ll ans;
inline int qread() {            //快读,不解释……
  char c=getchar();int num=0,f=1;
  for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
  for(;isdigit(c);c=getchar()) num=num*10+c-'0';
  return num*f;
}
struct Tree {
  ll maxx,lazy;
}tree[maxn<<2];
inline void pushdown(int rt) {    //下放lazy标记。
  if(tree[rt].lazy) {
    tree[ls].lazy+=tree[rt].lazy;
    tree[rs].lazy+=tree[rt].lazy;
    tree[rs].maxx+=tree[rt].lazy;
    tree[ls].maxx+=tree[rt].lazy;
    tree[rt].lazy=0;
  }
}
void modify(int rt,int l,int r,int L,int R,int val) { //区间修改,用于后面的更新。
  if(L>r||R<l) return;
  if(L<=l&&r<=R) {
    tree[rt].lazy+=val;
    tree[rt].maxx+=val;
    return;
  }
  pushdown(rt);
  int mid=(l+r)>>1;
  modify(ls,l,mid,L,R,val),modify(rs,mid+1,r,L,R,val);
  tree[rt].maxx=max(tree[ls].maxx,tree[rs].maxx);
}
int main() {
  n=qread(),m=qread();
  for(int i=1;i<=n;++i) f[i]=qread();
  for(int i=1;i<=m;++i) a[i]=qread();
  for(int i=n;i>=1;--i) nxt[i]=last[f[i]],last[f[i]]=i; //处理出nxt和last数组。
  for(int i=1;i<=m;++i) {
    if(last[i]) {                   //如果这个电影已经播放过。
      int zrj=nxt[last[i]];
      if(zrj) modify(1,1,n,last[i],zrj-1,a[i]);  
      //如果这不是最后一次播放这个电影,那么可以影响到的最右端点是nxt[last[i]]-1,然后last[i]就是左端点,也是第一次看,所以在这个区间加上这个电影的价值。
      else modify(1,1,n,last[i],n,a[i]);  //如果是最后一次,那么它将一直影响到最后。
    }
  }
  for(int i=1;i<=n;++i) {
    ans=max(ans,tree[1].maxx);         //每次更新一下最大值。
    int zrj=nxt[i];
    if(zrj) {                   //如果第二次播放。
      modify(1,1,n,i,zrj-1,-a[f[i]]);  //在这次和之后的一次的区间上价值减去这个电影的价值,因为相同电影看了价值为0。
      if(nxt[zrj]) modify(1,1,n,zrj,nxt[zrj]-1,a[f[i]]); //第二次和第三次之间加上这个电影的价值(因为是以第二次为左端点,只看了一次)。
      else modify(1,1,n,zrj,n,a[f[i]]); //不然就把第二次之后的加上这个价值。
    }
    else modify(1,1,n,i,n,-a[f[i]]);  //没有第二次播放,就从当前时间开始一直到最后,减去这个价值。
  }
  printf("%lld\n",ans);
  return 0;
}