BZOJ 3747: [POI2015]Kinoman 线段树

题目描述

共有m部电影,编号为1~m,第i部电影的好看值为w[i]。
在n天之中(从1~n编号)每天会放映一部电影,第i天放映的是第f[i]部。
你可以选择l,r(1<=l<=r<=n),并观看第l,l+1,…,r天内所有的电影。如果同一部电影你观看多于一次,你会感到无聊,于是无法获得这部电影的好看值。所以你希望最大化观看且仅观看过一次的电影的好看值的总和。

输入

第一行两个整数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)。

输出

输出观看且仅观看过一次的电影的好看值的总和的最大值。

样例输入

9 4
2 3 1 1 4 1 2 4 1
5 3 6 6

样例输出

15
样例解释:
观看第2,3,4,5,6,7天内放映的电影,其中看且仅看过一次的电影的编号为2,3,4。


 

在沈队的博客上看了这道题感觉不错,在来做的这道题。

这道题,我认为主要有两个难点:

1:

对于每一天,下一次该天播放的电影下次播放时间的处理。

2:

将电影对后面的影响转换到线段树上。

 

首先,我们定义线段树为区间最大值。

我们将每次电影的价值的表达形式变为:

这次该种电影到下次该种电影的区间都加上w[i]。

 

这样转换之后,我们在枚举每次播放电影的时候只需要取到1到n的区间中最大值,就是答案。

 

代码:

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

#define ll long long
#define il inline
#define db double

#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))

using namespace std;

il int gi()
{
	int x=0,y=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
		{
			if(ch=='-')
				y=-1;
			ch=getchar();
		}
	while(ch>='0'&&ch<='9')
		{
			x=x*10+ch-'0';
			ch=getchar();
		}
	return x*y;
}

il ll gl()
{
	ll x=0,y=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
		{
			if(ch=='-')
				y=-1;
			ch=getchar();
		}
	while(ch>='0'&&ch<='9')
		{
			x=x*10+ch-'0';
			ch=getchar();
		}
	return x*y;
}

struct node
{
	int l,r;
	ll s;
}c[4000045];

ll lazy[4000045];

il void pushdown(int rt)
{
	if(lazy[rt])
		{
			lazy[rt<<1]+=lazy[rt];
			lazy[(rt<<1)+1]+=lazy[rt];
			c[rt<<1].s+=lazy[rt];
			c[(rt<<1)+1].s+=lazy[rt];
			lazy[rt]=0;
		}
}

void add(int rt,int l,int r,int L,int R,ll num)
{
	if(L<=l&&R>=r)
		{
			c[rt].s+=num;
			lazy[rt]+=num;
			return;
		}
	pushdown(rt);
	int mid=(l+r)>>1;
	if(L<=mid)
		add(rt<<1,l,mid,L,R,num);
	if(R>mid)
		add((rt<<1)+1,mid+1,r,L,R,num);
	c[rt].s=max(c[rt<<1].s,c[(rt<<1)+1].s);
}

int next[1000045];//i th day next time to show

int day[1000045];//num i film last time appear

int f[1000045];

ll w[1000045];

int main()
{
	int n=gi(),m=gi();

	for(int i=1;i<=n;i++)
		f[i]=gi();
	
	for(int i=1;i<=m;i++)
		w[i]=gl();

	for(int i=n;i>=1;i--)//必须逆向才能求出来
		{
			next[i]=day[f[i]];
			day[f[i]]=i;
		}

	for(int i=1;i<=m;i++)//把每种电影第一段区间加上
		{
			if(!day[i])
				continue;
			if(next[day[i]])
				add(1,1,n,day[i],next[day[i]]-1,w[i]);
			else//只出现了一次
				add(1,1,n,day[i],n,w[i]);
		}

	ll ans=0;
	for(int i=1;i<=n;i++)
		{
			ans=max(ans,c[1].s);
			if(next[i])
				{
					add(1,1,n,i,next[i]-1,-w[f[i]]);//把当前到下次出现的区间减去
					if(next[next[i]])//为枚举到next[i]做准备
						add(1,1,n,next[i],next[next[i]]-1,w[f[i]]);
					else
						add(1,1,n,next[i],n,w[f[i]]);
				}
			else
				add(1,1,n,i,n,-w[f[i]]);
		}

	printf("%lld\n",ans);

	return 0;
}

 

posted @ 2017-10-17 17:23  GSHDYJZ  阅读(146)  评论(0编辑  收藏  举报