[POI2015]Kinoman 解题报告 (线段树)
题意
有 \(m\) 部电影, 每部电影有一个权值 \(w[i]\).
\(n\) 天内, 每天会播放一部电影 \(f[i]\).
若一部电影在区间 \([l,r]\) (代表天数) 内出现了两次以上, 则无法获得它的权值.
求能够获得的最大权值.
思路
先把左端点 \(l\) 放在 \(1\).
当一部电影出现了两次后, 就无法获得它的权值, 那么, 一部电影的贡献范围就是一个区间.
设 \(nxt[i]\) 为 \(f[i]\) 下一次出现的位置, 则 \(i\) 的贡献范围就是 \([i,nxt[i])\) (注意是左开右闭), 且 \(f[i]\) 没有在 \(i\) 之前的位置出现.
当 \(l=1\) 时, 我们用线段树区间修改, 把每部电影的贡献添加到它对应的区间上, 然后区间查询最大值即可.
当 \(l\) 往右移动时, 设 \(l-1=i\), 那么就把 \([i,nxt[i])\) 的贡献减去, 加上 \([nxt[i],nxt[nxt[i]])\) 的贡献, 然后再查询最大值即可.
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int _=1e6+7;
const int __=4e6+7;
int n,m,film[_],wgt[_],nxt[_],las[_];
ll maxn[__],tag[__],ans;
bool vis[_];
void upd(int k,ll w){
maxn[k]+=w;
tag[k]+=w;
}
void push_down(int k){
if(!tag[k]) return;
upd(k<<1,tag[k]);
upd(k<<1|1,tag[k]);
tag[k]=0;
}
void modify(int k,int l,int r,int x,int y,ll w){
if(l>=x&&r<=y){ upd(k,w); return; }
push_down(k);
int mid=(l+r)>>1;
if(x<=mid) modify(k<<1,l,mid,x,y,w);
if(y>mid) modify(k<<1|1,mid+1,r,x,y,w);
maxn[k]=max(maxn[k<<1],maxn[k<<1|1]);
}
ll query(int k,int l,int r,int x,int y){
if(l>=x&&r<=y) return maxn[k];
push_down(k);
int mid=(l+r)>>1;
ll t1=0,t2=0;
if(x<=mid) t1=query(k<<1,l,mid,x,y);
if(y>mid) t2=query(k<<1|1,mid+1,r,x,y);
return max(t1,t2);
}
int main(){
#ifndef ONLINE_JUDGE
freopen("x.in","r",stdin);
#endif
cin>>n>>m;
for(int i=1;i<=n;i++) scanf("%d",&film[i]);
for(int i=1;i<=m;i++){ scanf("%d",&wgt[i]); las[i]=n+1; }
for(int i=n;i>=1;i--){
nxt[i]=las[film[i]];
las[film[i]]=i;
}
for(int i=1;i<=n;i++)
if(!vis[film[i]]){
modify(1,1,n,i,nxt[i]-1,wgt[film[i]]);
vis[film[i]]=1;
}
for(int l=1;l<=n;l++){
ans=max(ans,query(1,1,n,l,n));
modify(1,1,n,l,nxt[l]-1,(ll)-wgt[film[l]]);
modify(1,1,n,nxt[l],nxt[nxt[l]]-1,(ll)wgt[film[l]]);
}
printf("%lld\n",ans);
return 0;
}