BZOJ3747[POI2015] Kinoman
Description
共有 m 部电影,编号为
1 ~ m ,第i 部电影的好看值为 w[i] 。在 n 天之中(从
1 ~ n 编号)每天会放映一部电影,第i 天放映的是第 f[i] 部。
你可以选择 l,r(1<=l<=r<=n) ,并观看第 l,l+1,…,r 天内所有的电影。如果同一部电影你观看多于一次,你会感到无聊,于是无法获得这部电影的好看值。所以你希望最大化观看且仅观看过一次的电影的好看值的总和。Input
第一行两个整数 n ,
m ( 1<=m<=n<=1000000 )。第二行包含 n 个整数
f[1],f[2],…,f[n](1<=f[i]<=m) 。第三行包含 m 个整数
w[1],w[2],…,w[m](1<=w[j]<=1000000) 。Output
输出观看且仅观看过一次的电影的好看值的总和的最大值。
Sample Input
9 4
2 3 1 1 4 1 2 4 1
5 3 6 6
Sample Output
15
样例解释:
观看第2,3,4,5,6,7天内放映的电影,其中看且仅看过一次的电影的编号为2,3,4。
Solution:
既然黄学长说这是线段树经典题,那它就是吧。想法确实是非常灵巧的。
考虑枚举左端点,我们尝试着直接算出每一个右端点的贡献值。
首先处理出
nxt[i]
为
A[i]
在
i
右边出现的第一个位置。当
#include<stdio.h>
#include<string.h>
#include<iostream>
#define M 1000001
#define ll long long
using namespace std;
int A[M],W[M],pos[M],Next[M];
struct Tree{
ll add,mx;
Tree(){
add=mx=0;
}
}Tree[M<<2];
inline void Rd(int &res){
char c;res=0;
while(c=getchar(),!isdigit(c));
do{
res=(res<<1)+(res<<3)+(c^48);
}while(c=getchar(),isdigit(c));
}
void up(int p){
Tree[p].mx=Tree[p<<1|1].mx;
if(Tree[p<<1].mx>Tree[p].mx)Tree[p].mx=Tree[p<<1].mx;
}
void down(int p){
if(Tree[p].add==0)return;
Tree[p<<1].add+=Tree[p].add;
Tree[p<<1|1].add+=Tree[p].add;
Tree[p<<1].mx+=Tree[p].add;
Tree[p<<1|1].mx+=Tree[p].add;
Tree[p].add=0;
}
void Update(int L,int R,int l,int r,int x,int p){
if(L==l&&R==r){
Tree[p].add+=x;
Tree[p].mx+=x;
return;
}
down(p);
int mid=(L+R)>>1;
if(r<=mid)Update(L,mid,l,r,x,p<<1);
else if(l>mid)Update(mid+1,R,l,r,x,p<<1|1);
else Update(L,mid,l,mid,x,p<<1),Update(mid+1,R,mid+1,r,x,p<<1|1);
up(p);
}
ll Query(int L,int R,int l,int r,int p){
if(L==l&&R==r)return Tree[p].mx;
down(p);
int mid=(L+R)>>1;
if(r<=mid)return Query(L,mid,l,r,p<<1);
else if(l>mid)return Query(mid+1,R,l,r,p<<1|1);
else{
ll tmp1=Query(L,mid,l,mid,p<<1),tmp2=Query(mid+1,R,mid+1,r,p<<1|1);
if(tmp1>tmp2)return tmp1;
else return tmp2;
}
}
int main(){
int n,m;
ll ans=0;
Rd(n);Rd(m);
for(int i=1;i<=n;i++)Rd(A[i]);
for(int i=1;i<=m;i++)Rd(W[i]);
memset(pos,-1,sizeof(pos));
for(int i=n;i>=1;i--){
Next[i]=pos[A[i]];
pos[A[i]]=i;
}
for(int i=1;i<=m;i++){
if(pos[i]==-1)continue;
if(Next[pos[i]]==-1)Update(1,n,pos[i],n,W[i],1);
else Update(1,n,pos[i],Next[pos[i]]-1,W[i],1);
}
for(int L=1;L<=n;L++){
ll res=Query(1,n,L,n,1);
if(res>ans)ans=res;
if(Next[L]!=-1)Update(1,n,L,Next[L]-1,-W[A[L]],1);
else Update(1,n,L,n,-W[A[L]],1);
if(Next[L]!=-1){
int nt=Next[Next[L]];
if(nt!=-1)Update(1,n,Next[L],nt-1,W[A[L]],1);
else Update(1,n,Next[L],n,W[A[L]],1);
}
}
cout<<ans<<endl;
return 0;
}
后记:这道题100万的数据,就会发现不同的线段树写法的常数差别还是很大的。
目前写法尽量采用上述将区间端点 LR 放在函数参数的写法,省空间也省时间。(然而并不知道为什么会变快)