51nod 1052 最大M子段和 & 1053 最大M子段和 V2
1052 dp即可
$dp[i][j]$ 表示前 $i$ 个数分成 $j$ 段。
$dp[i][j]=a[i]+\max\{dp[i-1][j], \max\{dp[k][j-1]\}\}$
前缀max优化一下即可
#include <bits/stdc++.h> #define ll long long const int N = 5100; ll dp[N][2]; int a[N], n, m; inline ll max(ll a, ll b) { return a > b ? a : b; } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) scanf("%d", a + i); if (m >= n) { ll ans = 0; for (int i = 1; i <= n; i++) ans += a[i]; printf("%lld\n", ans); return 0; } ll ans = 0; for (int j = 1; j <= m; j++) { ll mx = 0; int cur = j & 1, last = cur ^ 1; for (int i = 1; i <= n; i++) { dp[i][cur] = max(dp[i - 1][cur], mx) + a[i]; mx = max(dp[i][last], mx); ans = max(ans, dp[i][cur]); } } printf("%lld\n", ans); return 0; }
1053 考虑把连续负数和连续正数缩成一段,先把所有正数取了,然后从绝对值小到大考虑每一段,如果是一段负数,就把它加入,相当于他和左右两端缩在一起,如果是一段正数,就把它删去。用set维护。
#include <bits/stdc++.h> #define ll long long #define pii pair<ll, int> const int N = 5e4 + 7; ll a[N]; int pre[N], nxt[N], n, m; std::set<std::pii> st; void merge(int x) { int l = pre[x], r = nxt[x]; if (l) nxt[l] = r; if (r) pre[r] = l; } int main() { scanf("%d%d", &n, &m); ll ans = 0, sum = 0; int cnt = 0, tol = 0; for (int i = 1; i <= n; i++) { int x; scanf("%d", &x); if ((sum < 0 && x > 0) || (sum > 0 && x < 0)) { a[++cnt] = sum; tol += sum > 0; st.insert(std::pii(std::abs(sum), cnt)); sum = 0; } sum += x; if (x > 0) ans += x; } a[++cnt] = sum; tol += sum > 0; st.insert(std::pii(std::abs(sum), cnt)); for (int i = 1; i <= cnt; i++) { pre[i] = i - 1; nxt[i] = i + 1; } nxt[cnt] = a[0] = 0; while (tol > m) { int x = (*st.begin()).second; st.erase(st.begin()); if ((a[x] < 0 && (!pre[x] || !nxt[x])) || !a[x]) continue; if (pre[x]) st.erase(std::pii(std::abs(a[pre[x]]), pre[x])); if (nxt[x]) st.erase(std::pii(std::abs(a[nxt[x]]), nxt[x])); ans -= std::abs(a[x]); a[x] += a[pre[x]] + a[nxt[x]]; st.insert(std::pii(std::abs(a[x]), x)); merge(pre[x]); merge(nxt[x]); tol--; } printf("%lld\n", ans); return 0; }