题意简述
mmm个电影,编号111 ~ mmm,第iii部电影有一个好看值wiw_iwi。并且,每天都会放某一个电影,第iii天放第aia_iai部电影。请选出一段区间lll ~ rrr,使得这一段区间中只出现过一次的电影好看值和最大。
数据
输入
9 4
2 3 1 1 4 1 2 4 1
5 3 6 6
输出
15
解释
看222 ~ 777部电影,只出现过一次的是第222,333,444部电影,好看值和=3+6+6=15=3+6+6=15=3+6+6=15最大。
思路
这题是我一次考试题,然鹅并没有想出正解,直接n2n^2n2,由于老师比较仁慈,拿了404040分,就rank1了rank1了rank1了。
但是,O(n2)O(n^2)O(n2)只是土匪方法,要积极想想正解。
考完之后,翻了翻题解。其实,这题还是很简单的(只是提醒您不要对以下内容有所恐惧)。
首先我们会发现,枚举区间是必要的。那么,是枚举左端点还是右端点呢?
都枚举
显然不珂以。考虑到我们要记录出现了多次的电影,我更喜欢到前面找,所以就枚举右端点吧。除此之外,还需要记录一个preprepre数组,pre[i]pre[i]pre[i]表示a[i]a[i]a[i]这部电影上次出现的位置,=0=0=0表示第一次出现。(这个如何记录是入门难度,我就不细讲了)。
在枚举右端点iii的过程中,还要维护一个数组sss,s[j]s[j]s[j]表示选择jjj到iii这一段区间的答案。会发现,pre[i]+1pre[i]+1pre[i]+1到iii这一段是肯定只包含一个a[i]a[i]a[i]的,所以在右端点iii往后+1+1+1的时候,pre[i]+1pre[i]+1pre[i]+1到iii要区间加上w[a[i]]w[a[i]]w[a[i]]。但是也要考虑重复的答案,如果pre[i]pre[i]pre[i]非0,说明a[i]a[i]a[i]出现过多次,要减掉多算的答案。显然,这一段多算的就是pre[pre[i]]+1pre[pre[i]]+1pre[pre[i]]+1到pre[i]pre[i]pre[i],要区间减掉w[a[i]]w[a[i]]w[a[i]]。
那么,在右端点为iii时的答案就是max{s1,s2⋯si}max\{s_1,s_2\cdots s_i\}max{s1,s2⋯si}。
我们发现,在iii不断+1+1+1时,我们对sss数组做的更新操作有两种:
- 区间加/减
- 求区间最大值
这就变成了一个很裸的线段树,裸的有点R18R18R18。
最后,在所有的右端点答案中取最大即可。
代码:
#include<bits/stdc++.h>
#define N 1001000
#define int long long
using namespace std;
class SegmentTree//资瓷区间+-和询问最大值的线段树
{
private:
struct node
{
int l,r;
int s,a;
}tree[N<<2];
public:
#define ls index<<1
#define rs index<<1|1
#define L tree[index].l
#define R tree[index].r
#define S tree[index].s
#define A tree[index].a
void Update(int index)
{
S=max(tree[ls].s,tree[rs].s);
}
void BuildTree(int l,int r,int index)
{
L=l,R=r;
S=A=0;
if (l==r)
{
return;
}
int mid=(l+r)>>1;
BuildTree(l,mid,ls);
BuildTree(mid+1,r,rs);
Update(index);
}
void AddOne(int x,int index)
{
A+=x;
S+=x;
}
void PushDown(int index)
{
if (A)
{
AddOne(A,ls);
AddOne(A,rs);
A=0;
}
}
void Add(int l,int r,int x,int index)
{
if (l>R or L>r) return;
if (l<=L and R<=r)
{
AddOne(x,index);
return;
}
PushDown(index);
Add(l,r,x,ls);
Add(l,r,x,rs);
Update(index);
}
int Query(int l,int r,int index)
{
if (l>R or L>r) return 0;
if (l<=L and R<=r)
{
return S;
}
PushDown(index);
return max(Query(l,r,ls),Query(l,r,rs));
}
}T;
int n,m;
int a[N],w[N];
int pre[N],last[N];
void Input()
{
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
}
for(int i=1;i<=m;i++)
{
scanf("%lld",&w[i]);
}
for(int i=1;i<=n;i++)
{
pre[i]=last[a[i]],last[a[i]]=i;
}//处理pre数组
}
void Solve()
{
T.BuildTree(1,n,1);//写线段树一定不要忘了建树(当然有些写法珂以不写这句)
int ans=0;
for(int i=1;i<=n;i++)
{
T.Add(pre[i]+1,i,w[a[i]],1);//区间加上w[a[i]]
if (pre[i])
{
T.Add(pre[pre[i]]+1,pre[i],-w[a[i]],1);//减掉多算的w[a[i]]
}
ans=max(ans,T.Query(1,i,1));//取所有右端点答案的最大值
}
printf("%lld\n",ans);
}
main()
{
Input();
Solve();
return 0;
}
本文介绍了bzoj 3747题目,讨论如何找到一段区间,使得区间内只出现过一次的电影的好看值之和最大。通过枚举右端点,利用pre数组记录电影上次出现位置,并使用线段树处理区间加减和求最大值的问题,最终得出最优解。
388

被折叠的 条评论
为什么被折叠?



