[POI2015] BZOJ 3747 看电影

52 篇文章 0 订阅
3 篇文章 0 订阅

传送门

题解:

有点类似于扫描线的思想还是第一次见,感觉非常秒。

考虑移动左端点,欲通过一些奇技淫巧计算最优右端点。

首先左端点在1。考虑对于某一种电影,这种电影的第一次出现的位置记为h,下一个记为nxt[h]。(用nxt数组维护每个点下一个和它类型相同的电影的下标。如果没有则是n+1)

那么右端点在[h,nxt[h]-1]的时候会得到这部电影的好看值。这是一个区间加操作。把所有的第一次放映电影考虑完了查一下最大值即可。

现在考虑左端点在i的答案已经统计完了,移动到i+1,对右端点选择的影响。

那么注意到,右端点在[i,nxt[i]-1]无法获得这部电影的好看值了,区间减法。而在[nxt[i],nxt[nxt[i]]-1]处又可以获得这个好看值了,区间加法。

每次修改完了都来一个区间最大值询问。(其实直接询问整个区间或许也可以?)用线段树维护。

复杂度显然是O(nlgn)的。

然而我注意到,如果左端点从1开始枚举那么要先把一些区间加一遍,最坏情况下有一个常数2。

然而可以左端点从n枚举,实测会快一些。


代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 1000010
#define lint long long
using namespace std;
bool used[MAXN];int nxt[MAXN];
int a[MAXN],v[MAXN],h[MAXN];
struct segment{
    int l,r;lint pt,maxn;
    segment *ch[2];
}*rt;
inline int add_tags(segment* &rt,lint v)
{
    return rt->pt+=v,rt->maxn+=v;
}
inline int push_down(segment* &rt)
{
    add_tags(rt->ch[0],rt->pt);
    add_tags(rt->ch[1],rt->pt);
    rt->pt=0;return 0;
}
inline int push_up(segment* &rt)
{
    return rt->maxn=max(rt->ch[0]->maxn,rt->ch[1]->maxn);
}
int build(segment* &rt,int l,int r)
{
    rt=new segment;rt->l=l;rt->r=r;
    rt->pt=rt->maxn=0;rt->ch[0]=rt->ch[1]=NULL;
    if(l==r) return 0;int mid=(l+r)>>1;
    build(rt->ch[0],l,mid);
    build(rt->ch[1],mid+1,r);
    return 0;
}
int update(segment* &rt,int s,int t,lint v)
{
    int &l=rt->l,&r=rt->r;
    if(s<=l&&r<=t) return add_tags(rt,v);
    if(rt->pt) push_down(rt);
    int mid=(l+r)>>1;
    if(s<=mid) update(rt->ch[0],s,t,v);
    if(mid<t) update(rt->ch[1],s,t,v);
    push_up(rt);return 0;
}
lint query(segment* &rt,int s,int t)
{
    int &l=rt->l,&r=rt->r;
    if(s<=l&&r<=t) return rt->maxn;
    if(rt->pt) push_down(rt);
    int mid=(l+r)>>1;lint ans=0;
    if(s<=mid) ans=max(ans,query(rt->ch[0],s,t));
    if(mid<t) ans=max(ans,query(rt->ch[1],s,t));
    return ans;
}
int main()
{
    int n,m,maxa;scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=m;i++) scanf("%d",&v[i]);
    for(int i=1;i<=n;i++) maxa=max(maxa,a[i]);
    for(int i=1;i<=maxa;i++) h[i]=n+1;nxt[n+1]=n+1;
    for(int i=n;i>=1;i--) nxt[i]=h[a[i]],h[a[i]]=i;
    build(rt,1,n);
/*  for(int i=1;i<=n;i++)
        if(!used[a[i]])
        {
            used[a[i]]=true;
            update(rt,i,nxt[i]-1,v[a[i]]);
        }
    lint ans=0;
    for(int i=1;i<=n;i++)
    {
        ans=max(ans,query(rt,i,n));
        update(rt,i,nxt[i]-1,-v[a[i]]);
        if(nxt[i]<=n) update(rt,nxt[i],nxt[nxt[i]]-1,v[a[i]]);
    }*/
    lint ans=0;
    for(int i=n;i>=1;i--)
    {
        if(nxt[i]<=n) update(rt,nxt[i],nxt[nxt[i]]-1,-v[a[i]]);
        update(rt,i,nxt[i]-1,v[a[i]]);
        ans=max(ans,query(rt,i,n));
    }
    printf("%lld\n",ans);return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值