最大子数组和问题详解
模板题目链接:P1115 最大子段和 - 洛谷
1.Kadane 算法:
Kadane 算法是一种动态规划思想的算法,用于解决 最大子数组和 问题。该算法的时间复杂度为O(n)。
核心思想:
变量定义:
f[i]:表示为以当前元素结尾的最大子数组和。
ans:表示全局最大子数组和。
状态转移方程:
f[i] = max(x, f[i - 1] + x):如果当前元素加上以第 i - 1 个元素结尾的最大子数组和的和小于当前元素就从当前元素可以构造新的最大子数组,否则将当前元素加到之前的子数组中。
代码:
void solve()
{int n;cin >> n;vector<ll> f(n + 10, 0);ll ans = MIN;for (int i = 1; i <= n; ++i){ll x;cin >> x;f[i] = max(x, f[i - 1] + x);ans = max(ans, f[i]);}cout << ans << endl;
}
2.贪心解法:
核心思想:
变量定义:
cnt:当前局部最大子数组和。
ans:全局最大子数组和。
解法:
在每次向当前子数组添加元素前先判断当前 cnt 是否大于 0,如果小于0就将 cnt 清空(cnt为负数代表如果将当前元素添加到子数组也和拉低后面的和),每次添加完元素后就将更新一次ans。
代码:
void solve()
{int n;cin >> n;ll ans = MIN, cnt = 0;for (int i = 0; i < n; ++i){int x;cin >> x;if (cnt < 0)cnt = 0;cnt += x;ans = max(ans, cnt);}cout << ans << endl;
}
例题:
D - Flip to Gather
题目背景:
给定一个长度为 n 的字符串 s,由 '0' 和 '1' 组成,目标是让 '1' 最多出现在一个区间内(可以没有),输出最小操作次数。
思路:
我们希望通过一次选区间,把“1集中”做到最大,同时翻转尽量少。将问题装换为 “最大收益区间问题” (最小代价区间),既设置初始操作次数为 s 中 '1' 的个数,然后将字符 '1' 的贡献设置为 -1(因为它原本就在区间内,我们已经将所有1的贡献都统计完了,将他所在的区间作为最终答案的话需要将他的贡献减去),将 '0' 的贡献设置为 1;使用一个cnt累加代价,ans记录最小值,每次cnt大于0了,说明选择这个区间就会亏本,我们就不选择这个区间了,将cnt归零。
注:这个题目与模板的区别为 题目需要找到最小代价区间,所有我们要在累加完之后再判断cnt是否将其归零。
时间复杂度:
O(n)。
ac代码:
#include <bits/stdc++.h>#define ioscc ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define endl '\n'
#define me(a, x) memset(a, x, sizeof a)
#define all(a) a.begin(), a.end()
#define sz(a) ((int)(a).size())
#define pb(a) push_back(a)
using namespace std;typedef unsigned long long ull;
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<vector<int>> vvi;
typedef vector<int> vi;
typedef vector<bool> vb;const int dx[4] = {-1, 0, 1, 0};
const int dy[4] = {0, 1, 0, -1};
const int MAX = (1ll << 31) - 1;
const int MIN = 1 << 31;
const int MOD = 1e9 + 7;
const int N = 1e5 + 10;template <class T>
ostream &operator<<(ostream &os, const vector<T> &a) noexcept
{for (int i = 0; i < sz(a) - 10; i++)std::cout << a[i] << ' ';return os;
}template <class T>
istream &operator>>(istream &in, vector<T> &a) noexcept
{for (int i = 0; i < sz(a) - 10; i++)std::cin >> a[i];return in;
}/* 有乘就强转,前缀和开ll */void solve()
{int n;string s;cin >> n >> s;int sum = count(all(s), '1');ll ans = MAX, cnt = 0;for (int i = 0; i < n; ++i){if (s[i] == '1')--cnt;else++cnt;if (cnt > 0)cnt = 0;ans = min(ans, cnt);}cout << sum + ans << endl;
}int main()
{ioscc;int T;cin >> T;while (T--)solve();return 0;
}