题目描述
定义一个长度为 \(N\) 的 \(01\) 串 \(S\) 的得分为:
- 对于每个 \(1\le i\le M\),如果存在 \(l_i\le j\le r_i 且 S_j=1\),则将 \(a_i\) 添加到得分中。(\(a_i\) 可能为负数)
找到字符串的最大可能得分。
思路
我们试着将问题反过来:只有在 \([l_i,r_i]\) 中全是 \(0\),那么你将会失去 \(a_i\) 分。
令 \(dp_i\) 表示最后一个 \(1\) 在 \(i\) 时的最大分数。
如果说某个从 \(i\rightarrow j\) 的转移满足 \(i<l_k 且 j>r_k\),那么你将会失去 \(a_k\) 分,因为在 \([i+1,j-1]\) 中全都是 \(0\)。
可以考虑用线段树维护。我们对每个 \(i\) 在 \(r_i+1\) 处记录区间 \(i\)。那么当你在求 \(dp_j\) 时,就可以将所有该位置记录的 \([1,l_i-1]\) 的 \(dp\) 值减 \(a_i\)。因为这种转移符合上面的条件,应该减去。
空间复杂度 \(O(N+M)\),时间复杂度 \(O((N+M)\log N)\)。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using ll = long long;
using pii = pair<int, int>;const int MAXN = 200005;struct Segment_Tree {int l[MAXN << 2], r[MAXN << 2];ll Max[MAXN << 2], lazy[MAXN << 2];void build(int u, int s, int t) {l[u] = s, r[u] = t, Max[u] = lazy[u] = 0;if(s == t) {return;}int mid = (s + t) >> 1;build(u << 1, s, mid), build((u << 1) | 1, mid + 1, t);}void tag(int u, ll x) {Max[u] += x, lazy[u] += x;}void pushdown(int u) {tag(u << 1, lazy[u]), tag((u << 1) | 1, lazy[u]), lazy[u] = 0;}void update(int u, int s, int t, ll x) {if(l[u] >= s && r[u] <= t) {tag(u, x);return;}pushdown(u);if(s <= r[u << 1]) {update(u << 1, s, t, x);}if(t >= l[(u << 1) | 1]) {update((u << 1) | 1, s, t, x);}Max[u] = max(Max[u << 1], Max[(u << 1) | 1]);}ll Getmax(int u, int s, int t) {if(s > t) {return 0;}if(l[u] >= s && r[u] <= t) {return Max[u];}ll x = -(ll)(1e18);if(s <= r[u << 1]) {x = max(x, Getmax(u << 1, s, t));}if(t >= l[(u << 1) | 1]) {x = max(x, Getmax((u << 1) | 1, s, t));}return x;}
}tr;int n, m;
ll dp[MAXN], sum, Max;
vector<pii> ve[MAXN];signed main() {ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);cin >> n >> m;for(int i = 1, l, r, x; i <= m; ++i) {cin >> l >> r >> x;ve[r + 1].push_back({l, -x});sum += x;}tr.build(1, 1, n + 1);for(int i = 1; i <= n + 1; ++i) {for(auto [l, x] : ve[i]) {tr.update(1, 1, l, x);}dp[i] = tr.Getmax(1, 1, i);if(i <= n) {tr.update(1, i + 1, i + 1, dp[i]);}Max = max(Max, dp[i]);}cout << sum + Max;return 0;
}