BZOJ3747: [POI2015]Kinoman(线段树)

3747: [POI2015]Kinoman

1.思路

  对于此类问题,我们采用枚举右端点的方法来求解,当要添加第i天所要看的电影时,那么从上一次出现f[i]电影的地方pre[i]+1到第i天,我们加上好看值w[f[i]], 同时从pre[pre[i]]+1到pre[i], 因为存在重复,所以我们减去w[f[i]],然后求区间的最大值.

2.代码

 
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6+5;
#define ls rt<<1
#define rs rt<<1|1
typedef long long ll;
 
int f[N], w[N];
int pre[N], suf[N];
ll add[N<<2], v[N<<2];
 
inline void pushUp(int rt) {
    v[rt] = v[ls] > v[rs]? v[ls] : v[rs]; 
}
 
void pushDown(int rt) {
    if(add[rt]) {
        add[ls] += add[rt];
        add[rs] += add[rt];
        v[ls] += add[rt];
        v[rs] += add[rt];
        add[rt] = 0;
    }
}
 
void update(int rt, int l, int r, int a, int b, int c) {
    if(a <= l && r <= b) {
        v[rt] += (ll)c;
        add[rt] += (ll)c;
        return ;
    }
    int mid = (l + r) >> 1;
    pushDown(rt);
    if(a <= mid) {
        update(ls, l, mid, a, b, c);
    }
    if(b > mid) {
        update(rs, mid+1, r, a, b, c);
    }
    pushUp(rt);
}
 
int main() {
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++ i) {
        scanf("%d", &f[i]);
    }
    for(int i = 1; i <= m; ++ i) {
        scanf("%d", &w[i]);
    }
 
    for(int i = 1; i <= n; ++ i) {
        pre[i] = suf[f[i]];
        suf[f[i]] = i;
    }
 
    // 枚举右端点
    ll res = 0;
    for(int i = 1; i <= n; ++ i) {
        if(pre[i]) {
            update(1, 1, n, pre[pre[i]]+1, pre[i], -w[f[i]]);
        }
        update(1, 1, n, pre[i]+1, i, w[f[i]]);
        res = max(res, v[1]);
    }
 
    printf("%lld
", res);
    return 0;
}

  

原文地址:https://www.cnblogs.com/topk/p/7702501.html