一、基础数据结构——2.队列——3.双端队列和单调队列2
参考资料:《算法竞赛》,罗勇军 郭卫斌 著
本博客作为阅读本书的学习笔记,仅供交流学习。
建议关注 罗勇军老师博客
3. 单调队列与最大子序和问题
不限制子序列长度问题——贪心法或动态规划
HDOJ 1003 MAX SUM
Max Sum Time Limit: 2000/1000 MS (Java/Others)
Memory Limit: 65536/32768 K (Java/Others)
Problem Description
Given a sequence a[1],a[2],a[3]…a[n], your job is to calculate the max sum of a sub-sequence. For example, given (6,-1,5,4,-7), the max sum in this sequence is 6 + (-1) + 5 + 4 = 14.
Input
The first line of the input contains an integer T(1<=T<=20) which means the number of test cases. Then T lines follow, each line starts with a number N(1<=N<=100000), then N integers followed(all the integers are between -1000 and 1000).
Output
For each test case, you should output two lines. The first line is “Case #:”, # means the number of the test case. The second line contains three integers, the Max Sum in the sequence, the start position of the sub-sequence, the end position of the sub-sequence. If there are more than one result, output the first one. Output a blank line between two cases.
Sample Input
2
5 6 -1 5 4 -7
7 0 6 -1 1 -6 7 -5
Sample Output
Case 1: 14 1 4
Case 2: 7 1 6
Author
Ignatius.L
- 贪心法
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x7fffffff;
int main() {
int t; cin>>t;//测试用例个数
for (int i = 1; i <= t; ++i) {
int n; cin>>n;
int maxsum = -INF;//最大子序和,初始设为一个最小的int负数
int start = 1, end=1, p=1; //起点,终点,扫描位置
int sum=0;
for (int j = 1; j <= n; ++j) {
int a; cin>>a; sum+=a;//读入一个元素,累加
if (sum>maxsum){maxsum=sum;start=p;end=j;}
if (sum<0){//扫描到j时,若前面的最大子序和是负数,从下一个重新开始求和
sum=0;
p=j+1;
}
}
printf("Case %d:\n",i);
printf(" %d %d %d\n",maxsum,start,end);
if (i!=t) cout<<endl;
}
return 0;
}
- 动态规划
#include <bits/stdc++.h>
using namespace std;
int dp[100005];//dp[i]:以第i个数为结尾的最大值
int main(){
int t; cin>>t;//测试用例个数
for (int k = 1; k <= t; ++k) {
int n; cin>>n;
for (int i = 1; i <= n; ++i) cin>>dp[i];//用dp[]存储数据a[]
int start=1, end=1, p=1;//起点,终点,扫描位置
int maxsum = dp[1];
for (int i = 2; i <= n; ++i) {
if (dp[i-1]+dp[i]>=dp[i])//转移方程dp[i]=max(dp[i-1]+a[i],a[i])
dp[i]=dp[i-1]+dp[i];//dp[i-1]+a[i]比a[i]大
else p=i;//a[i]更大,则dp[i]=a[i]
if (dp[i]>maxsum){//dp[i]更大
maxsum=dp[i];start=p;end=i;//p开始,i结尾
}
}
printf("Case %d:\n",k);
printf(" %d %d %d\n",maxsum,start,end);
if (k!=t) cout<<endl;
}
return 0;
}
限制子序列长度问题——单调队列
#include <bits/stdc++.h>
using namespace std;
deque<int> dq;
int s[100005];
int main(){
int n,m;scanf("%d %d",&n,&m);
for (int i = 1; i < n; ++i) scanf("%lld",&s[i]);
for (int i = 1; i < n; ++i) s[i]=s[i-1]+s[i];//计算前缀和
int ans = -1e8;
dq.push_back(0);
for (int i = 1; i <= n; ++i) {
while(!dq.empty()&&dq.front()<i-m) dq.pop_front();//队头超过m范围:删头
if (dq.empty()) ans = max(ans,s[i]);
else ans= max(ans,s[i]-s[dq.front()]);//队头就是最小的s[k]
while(!dq.empty()&&s[dq.back()]>=s[i]) dq.pop_back();//队尾大于s[i]:去尾
dq.push_back(i);
}
printf("%d\n",ans);
return 0;
}