[BZOJ 3747] [POI 2015] Kinoman【线段树】
Problem Link : BZOJ 3747
解题的大致思路是,当区间的右端点向右移动一格时,只有两个区间的左端点对应的答案发生了变化。
从 f[i] + 1 到 i 的区间中的答案增加了 W[A[i]], 从 f[f[i]] + 1 到 f[i] 的区间的答案减少了 W[A[i]] ,其余区间的答案没有发生变化。
那么就是线段树的区间修改和区间最值查询。
代码如下:
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <cmath> #include <algorithm> using namespace std; const int MaxN = 1000000 + 5; int n, m; int A[MaxN], W[MaxN], Last[MaxN], F[MaxN]; typedef long long LL; LL Ans; LL T[MaxN * 4], D[MaxN * 4]; inline LL gmax(LL a, LL b) { return a > b ? a : b; } inline void Update(int x) { T[x] = gmax(T[x << 1], T[x << 1 | 1]); } inline void Read(int &num) { char c; c = getchar(); while (c < '0' || c > '9') c = getchar(); num = c - '0'; c = getchar(); while (c >= '0' && c <= '9') { num = num * 10 + c - '0'; c = getchar(); } } inline void Paint(int x, LL num) { D[x] += num; T[x] += num; } inline void PushDown(int x) { if (D[x] == 0) return; Paint(x << 1, D[x]); Paint(x << 1 | 1, D[x]); D[x] = 0; } LL Add(int x, int s, int t, int l, int r, int num) { if (l <= s && r >= t) { Paint(x, (LL)num); return T[x]; } PushDown(x); int m = (s + t) >> 1; LL ret = 0; if (l <= m) ret = gmax(ret, Add(x << 1, s, m, l, r, num)); if (r >= m + 1) ret = gmax(ret, Add(x << 1 | 1, m + 1, t, l, r, num)); Update(x); return ret; } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) { Read(A[i]); F[i] = Last[A[i]]; Last[A[i]] = i; } for (int i = 1; i <= m; i++) Read(W[i]); Ans = 0; for (int i = 1; i <= n; i++) { Ans = gmax(Ans, Add(1, 1, n, F[i] + 1, i, W[A[i]])); if (F[i] != 0) Ans = gmax(Ans, Add(1, 1, n, F[F[i]] + 1, F[i], -W[A[i]])); } printf("%lld\n", Ans); return 0; }