BZOJ 3747: [POI2015]Kinoman(线段树)

传送门

解题思路

  遇到这种选区间的题,很多时候都是枚举左端点,然后计算右端点。考虑枚举左端点之后如何维护,设下一部和(i)这个位置的电影相同的为(nxt[i]),那么如果左端点从(i)移动到(i+1)([i+1,nxt[i]-1])这段区间会减少(w[f[i]]),而([nxt[i],nxt[nxt[i]]-1)会增加(w[f[i]]),发现可以线段树维护。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>

using namespace std;
const int N=1000005;
typedef long long LL;

inline int rd(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)) f=ch=='-'?0:1,ch=getchar();
	while(isdigit(ch)) x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
	return f?x:-x;	
}

inline LL max(LL x,LL y){return x>y?x:y;}

int n,m,pre[N],w[N],nxt[N],cnt[N],f[N];
LL ans,Max[N<<2],tag[N<<2];

inline void pushdown(int x){
	Max[x<<1]+=tag[x];Max[x<<1|1]+=tag[x];
	tag[x<<1]+=tag[x];tag[x<<1|1]+=tag[x];
	tag[x]=0;	
}

void update(int x,int l,int r,int L,int R,LL k){
	if(L>R) return ;
	if(L<=l && r<=R) {Max[x]+=k;tag[x]+=k;return ;} 
	int mid=(l+r)>>1;if(tag[x]) pushdown(x);
	if(L<=mid) update(x<<1,l,mid,L,R,k);
	if(mid<R) update(x<<1|1,mid+1,r,L,R,k);
	Max[x]=max(Max[x<<1],Max[x<<1|1]);
}	

LL query(int x,int l,int r,int L,int R){
	if(L<=l && r<=R) return Max[x];
	int mid=(l+r)>>1;if(tag[x]) pushdown(x); 	
	if(L>mid) return query(x<<1|1,mid+1,r,L,R);
	else if(R<=mid) return query(x<<1,l,mid,L,R);
	else return max(query(x<<1,l,mid,L,R),query(x<<1|1,mid+1,r,L,R));
}

signed main(){
	n=rd(),m=rd();int x;
	for(int i=1;i<=n;i++){
		x=rd();f[i]=x;
		if(pre[x]) nxt[pre[x]]=i;
		pre[x]=i;	
	}
	LL now=0;
	for(int i=1;i<=m;i++) w[i]=rd();
	for(int i=1;i<=n;i++){
		if(!cnt[f[i]]) now+=w[f[i]];
		else if(cnt[f[i]]==1) now-=w[f[i]];
		cnt[f[i]]++;update(1,1,n,i,i,now);
	}
	for(int i=1;i<=n;i++){
		ans=max(ans,query(1,1,n,i,n));
		if(!nxt[i]) update(1,1,n,i+1,n,-w[f[i]]);
		else update(1,1,n,i+1,nxt[i]-1,-w[f[i]]);
		if(nxt[i] && !nxt[nxt[i]]) update(1,1,n,nxt[i],n,w[f[i]]);
		else update(1,1,n,nxt[i],nxt[nxt[i]]-1,w[f[i]]);
	}
	printf("%lld
",ans);
	return 0;
}	
原文地址:https://www.cnblogs.com/sdfzsyq/p/10261901.html