1. 程式人生 > >BZOJ 3747: [POI2015]Kinoman 線段樹

BZOJ 3747: [POI2015]Kinoman 線段樹

hellip highlight for etc sha app pac += 你會

題目描述

共有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;
}

BZOJ 3747: [POI2015]Kinoman 線段樹