[POI2015]Kinoman 解题报告 (线段树)

[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;
}
posted @ 2020-01-13 17:06  BruceW  阅读(144)  评论(0编辑  收藏  举报