一、基础数据结构——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

  1. 贪心法
#include 
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;
}
  1. 动态规划
#include 
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 
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;
}

你可能感兴趣的:(Algorithm,数据结构)