题目描述
共有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;
}