Kinoman bzoj-3747 POI-2015

题目大意:有m部电影,第i部电影的好看值为w[i]。现在放了n天电影,请你选择一段区间l~r使得l到r之间的好看值总和最大。特别地,如果同一种电影放了两遍及以上,那么这种电影的好看值将不会被获得。

注释:$1\le m \le n \le 10^6$。

想法:和rmq problem类似的,我们处理出每一个位置pos右边第一个和pos上电影种类相同的位置nxt[pos]。然后,我从1-n扫一遍,每次讲l+1到nxt[l]-1之间的值加上w[a[l]],这个过程可以用线段树维护。

最后,附上丑陋的代码... ...

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define N 1000050
#define lson pos<<1
#define rson pos<<1|1
typedef long long ll;
ll t[N<<2],lazy[N<<2];
int f[N],w[N],nxt[N],n,m,now[N];
char nc()
{
    static char buf[100000],*p1,*p2;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline int rd()
{
    register int x=0;
    register char s=nc();
    while(s<'0'||s>'9')s=nc();
    while(s>='0'&&s<='9')x=(x<<3)+(x<<1)+s-'0',s=nc();
    return x;
}
void pushdown(int pos)
{
	if(!lazy[pos]) return;
	ll d=lazy[pos];
	lazy[lson]+=d; lazy[rson]+=d;
	t[lson]+=d; t[rson]+=d;
	lazy[pos]=0;
}
void update(int l,int r,int x,int y,ll v,int pos)
{
	if(x<=l&&y>=r)
	{
		t[pos]+=v; lazy[pos]+=v;
		return;
	}
	pushdown(pos);
	int mid=(l+r)>>1;
	if(x<=mid) update(l,mid,x,y,v,lson);
	if(y>mid) update(mid+1,r,x,y,v,rson);
	t[pos]=max(t[lson],t[rson]);
}
int main()
{
	n=rd(),m=rd();
	for(int i=1;i<=n;i++)
	{
		f[i]=rd();
	}
	for(int i=1;i<=m;i++)
	{
		w[i]=rd();
	}
	for(int i=n;i;i--)
	{
		nxt[i]=now[f[i]];
		now[f[i]]=i;
	}
	for(int i=1;i<=m;i++)
	{
		if(now[i])
		{
			if(!nxt[now[i]]) update(1,n,now[i],n,w[i],1);
			else update(1,n,now[i],nxt[now[i]]-1,w[i],1);
		}
	}
	ll ans=0;
	for(int i=1;i<=n;i++)
	{
		ans=max(ans,t[1]);
		if(nxt[i])
		{
			update(1,n,i,nxt[i]-1,-w[f[i]],1);
			if(nxt[nxt[i]]) update(1,n,nxt[i],nxt[nxt[i]]-1,w[f[i]],1);
			else update(1,n,nxt[i],n,w[f[i]],1);
		}
		else update(1,n,i,n,-w[f[i]],1);
	}
	printf("%lld\n",ans);
	return 0;
}

小结:线段树能干好多事情啊qwq