原题
分类
动态规划
题意
计算从一个序列中最大连续子序列和、对应的起始元素和终止元素的位置。
输入/输出 | 要求与格式 |
---|---|
样例数的确定 | 最开始一行开始输入样例数 |
每个样例的输入 | 每行n + 1个数,第一个数n为数组元素个数,后n个数为序列的实际元素 |
输出结果 | 最大连续子序列和、对应的起始元素和终止元素的位置 |
输出格式 | 每个案例结果第一行为"Case X:",第二行为3个输出结果(空格隔开),两个案例结果间一个空行 |
特殊要求 | 有多个解的案例使用最前面的解 |
以下是数据范围:
数据 | 数据范围 |
---|---|
样例数 | |
序列元素个数 | |
序列元素 |
题解
这道题规定了数组元素个数最大可能有 个。
如果出于最暴力的方法来考虑,尝试每一种子序列的和,用前缀和的方法来优化求和,时间复杂度为 ,这样的计算方法肯定会TLE啊,必须换个思路。
其实在学动态规划的时候,我们老师讲的第一道例题就是这道,以下是动态规划的思路表格:
\ | 具体内容 |
---|---|
序列前 个数中,必须包含 的最大连续序列和 | |
初始状态 | |
状态转移方程 | |
计算结果 |
再来分析一下这个动态规划的合理性。
1、
的设计
最大连续子序列和必然是序列的前
个数中、以某个
结尾的一个序列。
2、状态转移方程
我们很容易看出,在这种
的设计下,为了使
能够达到最大值,
的计算只有两种选择:
- ①并入前面 对应的序列,继续拼接形成新的连续子序列。
- ②舍弃前面 对应的序列,自己本身单独形成一个新的连续子序列。
这两种方式我们要找到一个值最大的,也就是 ,其实和上面表格中列出的公式是一个意思,可能显得简化一些。
3、初始状态
最初始的状态很显然就是前1个数中必须包含
的最大连续子序列和,
.
4、计算结果
最终的结果就是所有
中,值最大而且还靠前的那组解。
对于位置信息,我们也可以另外写上一个start数组,用 来标记 对应序列的起始位置。每个案例的输出结果依次为 、 、 .
对于格式要求,两个案例间一个空行,可以理解为最后一个案例后不加空行。因此,只要在打印时,根据案例计数器作一个判断就可以了。
题解代码
HDU(C++/G++)AC代码如下:
#include <iostream>
#include <algorithm>
using namespace std;
int a[100005];
int dp[100005];
int start[100005];
int main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
//数据准备
int T;
int cnt = 1;
int n;
//样例数
cin >> T;
while (T--)
{
//接收输入
cin >> n;
for (int i = 1; i <= n; ++i)
cin >> a[i];
//初始化状态
dp[1] = a[1];
start[1] = 1;
//状态转移,开始dp
//状态转移方程:
// dp[x] = dp[x - 1] + a[x](dp[x - 1] >= 0)
// dp[x] = a[x](dp[x - 1] < 0)
for (int i = 2; i <= n; ++i)
{
if (dp[i - 1] >= 0)
{
dp[i] = dp[i - 1] + a[i];
start[i] = start[i - 1];
}
else
{
dp[i] = a[i];
start[i] = i;
}
}
//搜素结果
int maxt = 1;
for (int i = 2; i <= n; ++i)
if (dp[i] > dp[maxt])
maxt = i;
//输出结果
cout << "Case " << cnt++ << ':' << endl;
cout << dp[maxt] << ' ' << start[maxt] << ' ' << maxt << endl;
if (T)
cout << endl;
}
return 0;
}
评价
这道题算是一道动态规划(dp)的入门题,要想熟练使用dp,还是得多刷题长长见识啊。