原題
分類
動態規劃
題意
計算從一個序列中最大連續子序列和、對應的起始元素和終止元素的位置。
輸入/輸出 | 要求與格式 |
---|---|
樣例數的確定 | 最開始一行開始輸入樣例數 |
每個樣例的輸入 | 每行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,還是得多刷題長長見識啊。