BZOJ3747[POI2015] Kinoman

30 篇文章 0 订阅
23 篇文章 1 订阅

BZOJ3747[POI2015] Kinoman

Description

共有 m 部电影,编号为1~ m ,第i部电影的好看值为 w[i]

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

Input

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

Output

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

Sample Input

9 4

2 3 1 1 4 1 2 4 1

5 3 6 6

Sample Output

15

样例解释:

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

Solution:

既然黄学长说这是线段树经典题,那它就是吧。想法确实是非常灵巧的。

考虑枚举左端点,我们尝试着直接算出每一个右端点的贡献值。

首先处理出 nxt[i] A[i] i 右边出现的第一个位置。当L变为 L+1 时,可以发现区间 [L,nxt[L]1] (作为右端点)的贡献减少了 W[A[i]] ,区间 [nxt[L],nxt[nxt[L]]1] 的贡献增加了 W[A[i]] 。区间更新,区间最值,可以用线段树解决。

#include<stdio.h>
#include<string.h>
#include<iostream>
#define M 1000001
#define ll long long
using namespace std;
int A[M],W[M],pos[M],Next[M];
struct Tree{
    ll add,mx;
    Tree(){
        add=mx=0;
    }
}Tree[M<<2];
inline void Rd(int &res){
    char c;res=0;
    while(c=getchar(),!isdigit(c));
    do{
        res=(res<<1)+(res<<3)+(c^48);
    }while(c=getchar(),isdigit(c));
}
void up(int p){
    Tree[p].mx=Tree[p<<1|1].mx;
    if(Tree[p<<1].mx>Tree[p].mx)Tree[p].mx=Tree[p<<1].mx;
}
void down(int p){
    if(Tree[p].add==0)return;
    Tree[p<<1].add+=Tree[p].add;
    Tree[p<<1|1].add+=Tree[p].add;
    Tree[p<<1].mx+=Tree[p].add;
    Tree[p<<1|1].mx+=Tree[p].add;
    Tree[p].add=0;
}
void Update(int L,int R,int l,int r,int x,int p){
    if(L==l&&R==r){
        Tree[p].add+=x;
        Tree[p].mx+=x;
        return;
    }
    down(p);
    int mid=(L+R)>>1;
    if(r<=mid)Update(L,mid,l,r,x,p<<1);
    else if(l>mid)Update(mid+1,R,l,r,x,p<<1|1);
    else Update(L,mid,l,mid,x,p<<1),Update(mid+1,R,mid+1,r,x,p<<1|1);
    up(p);
}
ll Query(int L,int R,int l,int r,int p){
    if(L==l&&R==r)return Tree[p].mx;
    down(p);
    int mid=(L+R)>>1;
    if(r<=mid)return Query(L,mid,l,r,p<<1);
    else if(l>mid)return Query(mid+1,R,l,r,p<<1|1);
    else{
        ll tmp1=Query(L,mid,l,mid,p<<1),tmp2=Query(mid+1,R,mid+1,r,p<<1|1);
        if(tmp1>tmp2)return tmp1;
        else return tmp2;
    }
}
int main(){
    int n,m;
    ll ans=0;
    Rd(n);Rd(m);
    for(int i=1;i<=n;i++)Rd(A[i]);
    for(int i=1;i<=m;i++)Rd(W[i]);
    memset(pos,-1,sizeof(pos));
    for(int i=n;i>=1;i--){
        Next[i]=pos[A[i]];
        pos[A[i]]=i;
    }
    for(int i=1;i<=m;i++){
        if(pos[i]==-1)continue;
        if(Next[pos[i]]==-1)Update(1,n,pos[i],n,W[i],1);
        else Update(1,n,pos[i],Next[pos[i]]-1,W[i],1);
    }
    for(int L=1;L<=n;L++){
        ll res=Query(1,n,L,n,1);
        if(res>ans)ans=res;
        if(Next[L]!=-1)Update(1,n,L,Next[L]-1,-W[A[L]],1);
        else Update(1,n,L,n,-W[A[L]],1);
        if(Next[L]!=-1){
            int nt=Next[Next[L]];
            if(nt!=-1)Update(1,n,Next[L],nt-1,W[A[L]],1);
            else Update(1,n,Next[L],n,W[A[L]],1);
        }
    }
    cout<<ans<<endl;
    return 0;
}

后记:这道题100万的数据,就会发现不同的线段树写法的常数差别还是很大的。

目前写法尽量采用上述将区间端点 LR 放在函数参数的写法,省空间也省时间。(然而并不知道为什么会变快)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值