BZOJ3747: [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\n", res); return 0; }