【POI2015】【bzoj3747】Kinoman

题面传送门

题意就是要找一段连续区间使得这段区间内只放映过一次的电影的权值之和最大。

先考虑暴力,枚举区间左右端点l,r,由于每次r只向右拓展一位,所以统计答案时可以开个数组记一下出现次数,然后只考虑新增的那位能否被加入答案,时间代价O(n^2)

 

我们分析一下,暴力的思路其实可以理解成枚举l,然后对于每个固定的lO(n)地求出最优的r。假设枚举l不变,那么我们是不是可以施展一些膜法使得后面这个过程更加高效呢?

 

再仔细思考,认真分析,我们发现,假设左端点l就是1,那么我们其实可以O(mlogn)地求出r=1~n时的这n个答案,求法如下:

首先对于每天放映的电影,如果在这天之前已经放映过至少一次了,那么它就不会对这n个答案造成任何贡献。所以,当且仅当这个电影第一次在i这天放映,它会对r=i~next[i]-1这些答案产生贡献。(next[i]记录下一个与第i天放同一个电影的日子,可以O(n)预处理出来)

所以我们开一个线段树来执行区间加和查询最大值操作,O(m)地扫一遍所有电影若此电影在原序列中出现过,则把这个电影的贡献以区间加的形式加入线段树中。

序列中每个位置r对应线段树的一个叶子节点,节点信息记的是以1为区间左端点,r为区间右端点的答案。

 

然后考虑左移左端点l

每次li变成i+1,都会造成以下影响:

i~next[i]-1   -w[i]

Next[i]~next[next[i]-1  +w[i]

那么我们从1~n枚举l,对于每个llog(n)地在线段树中查询最优解,每个最优解取一下max就是最终的答案。

 

总结以下,正解做法是,我们先mlogn地处理出当l等于1时的线段树,然后将l后移,不断在原线段树上修改,然后查询答案,复杂度也是nlogn.

总复杂度O(nlogn)n范围1e6,但oj上开了60秒.(???),所以即使不加卡常技巧也可以轻松水过此题。

最后呈上AC代码:

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
#define N 1000005
#define ll long long
#define lc (x<<1)
#define rc (x<<1|1)
int n,m,f[N],w[N],nx[N],pr[N];
ll tr[N<<2],tag[N<<2];
inline int readin(){
    int x=0,flag=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')flag=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*flag;
}
inline void ud(int x){
    tr[x]=max(tr[lc],tr[rc]);
}
inline void psdn(int x){
    tr[lc]+=tag[x],tr[rc]+=tag[x];
    tag[lc]+=tag[x],tag[rc]+=tag[x];
    tag[x]=0;
}
void bt(int L,int R,int x){
    if(L==R)return;
    int mid=L+R>>1;
    bt(L,mid,lc),bt(mid+1,R,rc);
    ud(x);
}
void parch(int L,int R,int l,int r,ll v,int x){
    if(l==L&&r==R){ tr[x]+=v,tag[x]+=v; return; }
    if(tag[x])psdn(x);
    int mid=L+R>>1;
    if(r<=mid)parch(L,mid,l,r,v,lc);
    else if(l>mid)parch(mid+1,R,l,r,v,rc);
    else parch(L,mid,l,mid,v,lc),parch(mid+1,R,mid+1,r,v,rc);
    ud(x);
}
ll ans;
int main(){
    n=readin();m=readin();
    for(register int i=1;i<=n;i++)f[i]=readin();
    for(register int i=1;i<=m;i++)w[i]=readin();
    for(register int i=n;i>=1;i--){
        nx[i]=pr[f[i]];
        pr[f[i]]=i;
    }
    bt(1,n,1);
    int p=0;
    for(register int i=1;i<=m;++i){
        if(!pr[i])continue;
        p=nx[pr[i]]?(nx[pr[i]]-1):n;
        parch(1,n,pr[i],p,w[i],1);
    }
    for(register int i=1;i<=n;++i){
        ans=max(ans,tr[1]);
        if(nx[i]){
            parch(1,n,i,nx[i]-1,-w[f[i]],1);
            if(nx[nx[i]]) parch(1,n,nx[i],nx[nx[i]]-1,w[f[i]],1);
            else parch(1,n,nx[i],n,w[f[i]],1);
        }
        else parch(1,n,i,n,-w[f[i]],1); 
    }
    printf("%lld",ans);
}

 

最后弱弱地求支持本题解博客:https://blog.csdn.net/yy0944?ref=toolbar

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值