Parsing algorithm contest theme (5): simple data structures

This series is an extension of this algorithm textbook: "algorithm contest entry to advanced" ( Jingdong Dangdang ), Tsinghua University Press
PDF Download: https://github.com/luoyongjun999/code one of the "additional information"
if recommended , please contact: (1) QQ group 567,554,289; (2) of QQ, 15512356

  This article written just learned programming languages, is learning new players data structure.

  "Data structure" textbooks, these elements generally comprises: a linear table (array, linked list), stacks and queues, strings, arrays, and generalized multidimensional tables, hashing, and binary trees, view (memory map, the traversal), ranking.
  In this paper, a few simple exercises detailed code and data structures: linked lists, stacks and queues.
  Several other data structures and code exercises, for example a string, a binary tree, FIG., In "Advanced algorithm contest entry to" book has a detailed description will not be repeated here.

1 list

  List of features is: a set of any data element storage unit stores the linear form (group of memory cells which may be continuous, or may not be continuous). The list is easy to understand and operate basic data structures, and its actions are: initialization, adding, traverse, insert, delete, search, sort, release and so on.
  The following example using Rockwell valley P1996, given the dynamic chain, the static chain, 5 kinds of the STL list implementation. There singly linked list, there are a doubly linked list. In the competition, to speed up the encoding speed, usually with a static list or STL list.
  In this paper, five kinds of code, after the author's detailed order, logic and process exactly the same, understand one another completely analogous, we can focus on the different implementations, easy to learn.
----------------------------------------------
Los Valley P1996 HTTPS : //www.luogu.com.cn/problem/P1996
Joseph problems
subject description : n individual circle, beginning from the first number of individual packets, the number of people out of m column, and then again from the next person 1 began to count off the number of people and then to m a circle, and so on, until everyone turns out, please turn out the circle of people in the output number.
O : input two integers n, m. N integers output line, the output of each coil in sequence a number of people. 1≤m, n≤100.
Sample input and output :
Input
103
Output
36927185104
------------------------------- ---------------

1.1 Dynamic list

  Textbooks will speak dynamic list, it takes temporary assignment list node, node list released after use. In doing so, the advantage of freeing up space in time, do not use extra memory. The disadvantage is that it is prone to error.
  The following code implements a dynamic way linked list.

#include <bits/stdc++.h>
struct node{          //链表结构
    int data;
    node *next;
};

int main(){
    int n,m;
    scanf("%d %d",&n,&m);
    node *head,*p,*now,*prev;   //定义变量
    head = new node; head->data = 1; head->next=NULL; //分配第一个节点,数据置为1        
    now = head;                 //当前指针是头
    for(int i=2;i<=n;i++){
        p = new node;  p->data = i; p->next = NULL;  //p是新节点
        now->next = p;        //把申请的新节点连到前面的链表上
        now = p;              //尾指针后移一个
    }    
    now->next = head;            //尾指针指向头:循环链表建立完成

 //以上是建立链表,下面是本题的逻辑和流程。后面4种代码,逻辑流程完全一致。

    now = head, prev=head;      //从第1个开始数
    while((n--) >1 ){ 
        for(int i=1;i<m;i++){       //数到m,停下
            prev = now;             //记录上一个位置,用于下面跳过第m个节点
            now = now->next; 
        }
        printf("%d ", now->data);       //输出第m节点,带空格
        prev->next = now->next;         //跳过这个节点
        delete now;                     //释放节点
        now = prev->next;               //新的一轮        
    }
    printf("%d", now->data);            //打印最后一个,后面不带空格
    delete now;                         //释放最后一个节点
    return 0;
}

1.2 with a static one-way linked list structure

  The above dynamic list, you need to allocate and free space, although the use of space is saved, but prone to error. In the contest, the memory management is not strictly required, to speed up the encoding speed, generally static allocation, eliminating the need for dynamic allocation and release of trouble. This static list, using a large array to store the list of pre-allocated.
  Static lists there are two ways, one is almost define a linked list structure, and the dynamic linked list structure; is to use a one-dimensional array, a linked list operations directly on the array.
  Three examples given herein: one-way linked list structure for a static, two-way linked list structure for static, the static one-way linked list with one-dimensional array.
  The following is a way to realize the structure for a static list.

#include <bits/stdc++.h>
const int maxn = 105;        //定义静态链表的空间大小
struct node{                 //单向链表
    int id;
    //int data;   //如有必要,定义一个有意义的数据
    int nextid;
}nodes[maxn];

int main(){
    int n, m;
    scanf("%d%d", &n, &m);
    nodes[0].nextid = 1;
    for(int i = 1; i <= n; i++){
        nodes[i].id = i;
        nodes[i].nextid = i + 1;
    }
    nodes[n].nextid = 1;                     //循环链表:尾指向头

    int now = 1, prev = 1;                   //从第1个开始
    while((n--) >1){
        for(int i = 1; i < m; i++){          //数到m,停下
            prev = now;  
            now = nodes[now].nextid;
        }
        printf("%d ", nodes[now].id);        //带空格
        nodes[prev].nextid = nodes[now].nextid;  //跳过节点now,即删除now
        now = nodes[prev].nextid;            //新的now
    }    
    printf("%d", nodes[now].nextid);         //打印最后一个,后面不带空格
    return 0; 
}

1.3 two-way linked list with a static structure

#include <bits/stdc++.h>
const int maxn = 105;
struct node{      //双向链表
    int id;       //节点编号
    //int data;   //如有必要,定义一个有意义的数据
    int preid;    //前一个节点
    int nextid;   //后一个节点
}nodes[maxn];

int main(){
    int n, m;
    scanf("%d%d", &n, &m);
    nodes[0].nextid = 1;
    for(int i = 1; i <= n; i++){  //建立链表
        nodes[i].id = i;
        nodes[i].preid = i-1;     //前节点
        nodes[i].nextid = i+1;    //后节点
    }
    nodes[n].nextid = 1;          //循环链表:尾指向头
    nodes[1].preid = n;           //循环链表:头指向尾

    int now = 1;                  //从第1个开始
    while((n--) >1){
        for(int i = 1; i < m; i++)     //数到m,停下
            now = nodes[now].nextid;
        printf("%d ", nodes[now].id);  //打印,后面带空格

        int prev = nodes[now].preid;   
        int next = nodes[now].nextid;
        nodes[prev].nextid = nodes[now].nextid;  //删除now
        nodes[next].preid = nodes[now].preid;   
        now = next;                    //新的开始
    }    
    printf("%d", nodes[now].nextid);   //打印最后一个,后面不带空格
    return 0; 
}

1.4 one-dimensional array of one-way static list

  This is the simplest implementation. Define a one-dimensional array \ (Nodes [] \) , \ (Nodes [I] \) a \ (I \) is the value of node, \ (Nodes [I] \) values are next node.
  It can be seen from the above description, its use environment is also limited because it can store a data node is \ (I \) .

#include<bits/stdc++.h>
int nodes[150];
int main(){   
    int n, m;
    scanf("%d%d", &n, &m); 
    for(int i=1;i<=n-1;i++)          //nodes[i]的值就是下一个节点
        nodes[i]=i+1;
    nodes[n]=1;                      //循环链表:尾指向头

    int now = 1, prev = 1;           //从第1个开始
    while((n--) >1){
        for(int i = 1; i < m; i++){   //数到m,停下
            prev = now;  
            now = nodes[now];         //下一个
        }
        printf("%d ", now);  //带空格
        nodes[prev] = nodes[now];     //跳过节点now,即删除now
        now = nodes[prev];            //新的now
    }    
    printf("%d", now);                //打印最后一个,不带空格
    return 0;
}

1.5 STL list

  Race or projects, often using C ++ STL list. list is a doubly linked list, its memory space may be discontinuous, the data to be accessed by the pointer, and can efficiently remove inserted anywhere, insert and delete operations are time constants.
  The reader is familiar with their own list of initialization, add, traverse, insert, delete, search, sort, release [ reference ] .

  The following is a list of Los Valley P1996 achieve.

#include <bits/stdc++.h>
using namespace std;

int main(){
    int n, m;
    cin>>n>>m;
    list<int>node;
    for(int i=1;i<=n;i++)         //建立链表
        node.push_back(i);     
    list<int>::iterator it = node.begin();
    while(node.size()>1){         //list的大小由STL自己管理
        for(int i=1;i<m;i++){     //数到m
             it++; 
             if(it == node.end()) //循环链表,end()是list末端下一位置
                it = node.begin();                                              
        }
        cout << *it <<"";
        list<int>::iterator next = ++it;
        if(next==node.end())  next=node.begin();  //循环链表
        node.erase(--it);         //删除这个节点,node.size()自动减1
        it = next;
    }
    cout << *it;
    return 0;
 }

1.6 Exercises list

  Bestseller "prove safety offer" gives a list of exercises OJ address: https://leetcode-cn.com/problemset/lcof/
  where these problems is the list exercises:
  face questions 06- to print the list from the tail to the head
  face questions 22- the list penultimate k nodes
  face questions 24- inverted list
  face questions merge two ordered lists 25
  questions 35 face complex chain of copy
  face questions 52- two first common node list of
  interview questions 18 deleted the list of nodes

2 queue

  Data access mode queue is "FIFO." For example canteen Dafan team, first come first serve.
  Queues implemented in two ways: a circular queue and the queue chain.


  Chain queue, it can be seen as a special case of the single linked list, with each node connected to the pointer.
  Circular queue, a sequence table, a set of contiguous memory locations storing sequentially queue element, with two pointers respectively indicate front and rear queue head and queue tail element element. Since the FIFO queue is a "team", so in the storage unit, front and rear are straight ahead, walk all the storage space may overflow. To solve this problem, the queue is designed as a ring of circular queue.
  The main problem is to find a queue and stack is slow, requires a lookup from start to finish. In some applications, you can use the priority queue, so that the highest priority (such as maximum number), first-out queue.
  Because the queue is very simple, and often a fixed size, so it is generally used in a competition to static array queue, or the use of STL queue.
  Here is an example, at 2.1 and 2.2 respectively given STL queue static array and two types of code.
----------------------------------------------
Los Valley P1540 HTTPS : //www.luogu.com.cn/problem/P1540
machine translation
Title description : there are M memory units, each able to store a translated word and sense. Whenever the software is stored in memory before a new word, if the number of words already stored in the current memory does not exceed M-1, the memory unit stores a new word software will not be used; if the memory has been stored in the M word, word software will clear the memory of the first to enter, freeing unit, store the new words.
Assume an English article length of N words. Given this article to be translated, the translation software needed to find how many times the external memory dictionary? Before assuming that translation start, there is no word in memory.
Input : A total of 2 lines. Each line separated by a space between the two numbers.
The first two acts of positive integers M, the length N, and represents the memory capacity of the article.
The second line of N non-negative integers, in the order of articles, each number (size of 1000) on behalf of an English word. Two words are articles in the same word, if and only if they are the same as corresponding non-negative integer. .
Output : An integer number of dictionary software for the needs of the investigation.
Sample input and output :
Input
. 3. 7
. 1. 1 2 5. 4. 4. 1
Output
5
--------------------------------- -------------

2.1 STL queue

  On the operation of the STL queue:
  Queue q; // define the stack, Type data type, such as int, float, char, etc.
  q push (item);. // put item into a queue
  q.front (); // returns the first element of the team, but does not delete
  q.pop (); // delete the first team element
  q.back (); // returns the tail element
  q.size (); // returns the number of elements
  q.empty (); // check whether the queue is empty
  below Los Valley P1540 code, because they are not themselves manage the queue, the code is very simple.
  There is no word method of checking memory note code. If a search for a place, too slow; not only quickly but also with the hash code is simple.

#include<bits/stdc++.h>
using namespace std;

int hash[1003]={0};  //用hash检查内存中有没有单词,hash[i]=1表示单词i在内存中
queue<int> mem;      //用队列模拟内存

int main(){
    int m,n;
    scanf("%d%d",&m,&n);
    int cnt=0;          //查词典的次数
    while(n--){ 
    int en;
    scanf("%d",&en);    //输入一个英文单词
    if(!hash[en]){      //如果内存中没有这个单词
        ++cnt; 
        mem.push(en);   //单词进队列,放到队列尾部
        hash[en]=1;     //记录内存中有这个单词
        while(mem.size()>m){         //内存满了
            hash[mem.front()] = 0;   //从内存中去掉单词
            mem.pop();               //从队头去掉
            }
        }
    }
    printf("%d\n",cnt);
    return 0;
}

2.2 handwriting circular queue

  下面是循环队列的模板。代码中给出了静态分配空间和动态分配空间两种方式。竞赛中用静态分配更好。

#include<bits/stdc++.h>
#define MAXQSIZE 1003      //队列大小
int hash[MAXQSIZE]={0};    //用hash检查内存中有没有单词

struct myqueue{                  
    int data[MAXQSIZE];    //分配静态空间
    /* 如果动态分配,就这样写: int *data;    */
    int front;             //队头,指向队头的元素
    int rear;              //队尾,指向下一个可以放元素的空位置

    bool init(){           //初始化
    /*如果动态分配,就这样写:
        Q.data = (int *)malloc(MAXQSIZE * sizeof(int)) ; 
        if(!Q.data) return false; */
        front = rear = 0;
        return true;
    }
    int size(){            //返回队列长度
        return (rear - front + MAXQSIZE) % MAXQSIZE;
    }
    bool push(int e){      //队尾插入新元素。新的rear指向下一个空的位置
         if((rear + 1) % MAXQSIZE == front ) return false;    //队列满
         data[rear] = e;
         rear = (rear + 1) % MAXQSIZE;
         return true;
    }
    bool pop(int &e){      //删除队头元素,并返回它
         if(front == rear) return false;   //队列空
         e = data[front];
         front = (front + 1) % MAXQSIZE;
         return true;
    }
}Q;  

int main(){
    Q.init();   //初始化队列
    int m,n;  scanf("%d%d",&m,&n);
    int cnt = 0;
    while(n--){ 
    int en;  scanf("%d",&en);    //输入一个英文单词
    if(!hash[en]){               //如果内存中没有这个单词
        ++cnt;
        Q.push(en);              //单词进队列,放到队列尾部
        hash[en]=1;
        while(Q.size()>m){       //内存满了
                int tmp;
                Q.pop(tmp);      //删除队头
            hash[tmp] = 0;       //从内存中去掉单词             
            }
        }
    }
    printf("%d\n",cnt);
    return 0;
}

2.3 单调队列

  前面讲的队列,是很“规矩”的,队列的元素都是“先进先出”,队头的只能弹出,队尾只能进入。有没有不那么“规矩”的队列呢?这就是单调队列,它有2个特征:
  (1)队列中的元素是单调有序的,且元素在队列中的顺序和原来在序列中的顺序一致;
  (2)单调队列的队头和队尾都能入队和出队。
  其中(1)是我们期望的结果,它是通过(2)来实现的。
  单调队列用起来非常灵活,在很多问题中应用它可以获得优化。简单地说是这样实现的:序列中的n个元素,用单调队列处理时,每个元素只需要进出队列一次,复杂度是O(n)。
  下面用两个模板题来讲解单调队列的应用,了解它们如何通过单调队列获得优化。注意队列中“删头、去尾、窗口”的操作。

2.3.1 滑动窗口


洛谷 P1886 https://www.luogu.com.cn/problem/P1886
滑动窗口 /【模板】单调队列
题目描述:有一个长为 n 的序列 a,以及一个大小为 k 的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。
例如:
The array is [1,3,-1,-3,5,3,6,7], and k = 3。

输入输出:输入一共有两行,第一行有两个正整数 n,k。 第二行 n 个整数,表示序列 a。输出共两行,第一行为每次窗口滑动的最小值,第二行为每次窗口滑动的最大值。
注意:\(1 ≤ k ≤ n ≤ 10^{6},a_i\in[-2^{31}, 2^{31}]\)
输入输出样例
输入
8 3
1 3 -1 -3 5 3 6 7
输出
-1 -3 -3 -3 3 3
3 3 5 5 6 7
----------------------------------------------
  这一题用暴力法很容易编程,从头到尾扫描,每次检查\(k\)个数,一共检查\(O(nk)\)次。暴力法显然会超时,这一题需要用\(O(n)\)的算法。
  下面用单调队列来求解,它的复杂度是\(O(n)\)的。
  在这一题中,单调队列有以下特征
  (1)队头的元素始终是队列中最小的;根据题目需要输出队头,但是不一定弹出。
  (2)元素只能从队尾进入队列,从队头队尾都可以弹出。
  (3)序列中的每个元素都必须进入队列。例如a进队尾时,和原队尾b比较,如果a≤b,就从队尾弹出b;弹出队尾所有比a大的,最后a进入队尾。入队的这个操作,保证了队头元素是队列中最小的。
  直接看上述题解可能有点晕,这里以食堂排队打饭为例子来说明它。
  大家到食堂排队打饭时都有一个心理,在打饭之前,先看看里面有什么菜,如果不好吃就走了。不过,能不能看到和身高有关,站在队尾的人如果个子高,眼光能越过前面队伍的脑袋,看到里面的菜;如果个子矮,会被挡住看不见。
  矮个子希望,要是前面的人都比他更矮就好了。如果他会魔法,他来排队的时候,队尾比他高的就自动从队尾离开,新的队尾如果仍比他高,也会离开。最后,新来的矮个子成了新的队尾,而且是最高的。他终于能看到菜了,让人兴奋的是,菜很好吃,所以他肯定不会走。
  假设每一个新来的魔法都比队列里的人更厉害,这个队伍就会变成这样:每个新来的人都能排到队尾,但是都会被后面来的矮个子赶走。这样一来,这个队列就会始终满足单调性:从队头到队尾,由矮到高。
  但是,让这个魔法队伍郁闷的是,打饭阿姨一直忙她的,顾不上打饭。所以排头的人等了一会儿,就走了,等待时间就是k。这有一个附带的现象:队伍长度不会超过k。
  输出什么呢? 每当新来一个排队的人,如果排头还没走,就跟阿姨喊一声,这就是输出。
  以上是本题的现实模型。
  下面举例描述算法流程,队列是{\(1,3,-1,-3,5,3,6,7\)},读者可以把数字想象成身高。以输出最小值为例,下面表格中的“输出队首”就是本题的结果。
元素进入队尾| 元素进队顺序| 队列| 窗口范围| 队首在窗口内吗?|输出队首|弹出队尾|弹出队首

|-: | -: | -: | -: | -:| -:| -:| -:|
|1| 1| {1}| [1]| 是| | | |
|3| 2| {1,3}| [1 2]| 是| | | |
|-1| 3| {-1}| [1 2 3]| 是| -1| 3,1| |
|-3| 4| {-3}| [2 3 4]| 是| -3| -1| |
|5| 5| {-3,5}| [3 4 5]| 是| -3| | |
|3| 6| {-3,3}| [4 5 6]| 是| -3| 5| |
|6| 7| {3,6}| [5 6 7]| -3否,3是| 3| | -3|
|7| 8| {3,6,7}| [6 7 8]| 是| 3| | |
  单调队列的时间复杂度:每个元素最多入队1次、出队1次,且出入队都是\(O(1)\)的,因此总时间是\(O(n)\)。题目需要逐一处理所有\(n\)个数,所以\(O(n)\)已经是能达到的最优复杂度。
  从以上过程可以看出,单调队列有两个重要操作:删头、去尾。
  (1)删头。如果队头的元素脱离了窗口,这个元素就没用了,弹出它。
  (2)去尾。如果新元素进队尾时,原队尾的存在破坏了队列的单调性,就弹出它。
  读者可以自己写一个单调队列,不过,一般用STL deque就好了。deque是双端队列,它的用法是:
  q[i]:返回q中下标为i的元素;
  q.front():返回队头;
  q.back():返回队尾;
  q.pop_back():删除队尾。不返回值;
  q.pop_front():删除队头。不返回值;
  q.push_back(e):在队尾添加一个元素e;
  q.push_front(e):在队头添加一个元素e。
  下面是P1886的代码[参考]

#include<bits/stdc++.h>
using namespace std;

int a[1000005];
deque<int>q;        //队列中的数据,实际上是元素在原序列中的位置

int main(){
    int n,m;
    scanf("%d%d",&n,&m);  
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);  
    for(int i=1;i<=n;i++){                       //输出最小值 
        while(!q.empty() && a[q.back()]>a[i])    //去尾
            q.pop_back(); 
        q.push_back(i);
        if(i>=m){                                //每个窗口输出一次
            while(!q.empty() && q.front()<=i-m)  //删头 
                q.pop_front();
            printf("%d ", a[q.front()]);
        }
    }
    printf("\n");

    while(!q.empty())  q.pop_front();            //清空,下面再用一次
    for(int i=1;i<=n;i++){                       //输出最大值 
        while(!q.empty() && a[q.back()]<a[i])    //去尾
            q.pop_back();     
        q.push_back(i);
        if(i>=m){
            while(!q.empty() && q.front()<=i-m)  //删头
                q.pop_front(); 
            printf("%d ", a[q.front()]);
        }
    }
    printf("\n");
    return 0;
}

2.3.2 最大子序和

  给定长度为n的整数序列A,它的“子序列”定义是:A中非空的一段连续的元素。子序列和,例如序列(6,-1,5,4,-7),前4个元素的和是6 + (-1) + 5 + 4 = 14。
  最大子序和问题,按子序列有无长度限制,有两种:
  (1)不限制子序列的长度。在所有可能的子序列中,找到一个子序列,该子序列和最大。
  (2)限制子序列的长度。给一个限制m,找出一段长度不超过m的连续子序列,使它的和最大。
  问题(1)比较简单,用贪心或DP,复杂度都是O(n)的。
  问题(2)用单调队列,复杂度也是O(n)的。通过这个例子,读者可以理解为什么单调队列能用于DP优化
  问题(1)不是本节的内容,不过为了参照,下面也给出题解。

1. 问题(1)的求解
  用贪心或DP,在O(n)时间内求解。例题是hdu 1003。
----------------------------------------------
hdu 1003 http://acm.hdu.edu.cn/showproblem.php?pid=1003
Max Sum
题目描述:给一个序列,求最大子序和。
输入:第1行是整数T,表示测试用例个数,1<=T<=20。后面跟着T行,每一行第1个数是N,后面是N个数,1<=N<=100000,每个数在[-1000, 1000]内。
输出:对每个测试,输出2行,第1行是"Case #:",其中"#"是第几个测试,第2行输出3个数,第1个数是最大子序和,第2和第3个数是开始和终止位置。
输入输出样例
输入
2
5 6 -1 5 4 -7
7 0 6 -1 1 -6 7 -5
输出
Case 1:
14 1 4

Case 2:
7 1 6
----------------------------------------------
题解1:贪心。 逐个扫描序列中的元素,累加。加一个正数时,和会增加;加一个负数时,和会减少。如果当前得到的和变成了负数,这个负数和在接下来的累加中,会减少后面的求和。所以抛弃它,从下一位置开始重新求和。
  hdu 1003的贪心代码:

#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 start=1, end=1, p=1;   //起点,终点,扫描位置
        for(int j = 1; j <= n; j++){
            int a; cin >> a;       //读入一个元素
            int sum = 0;           //子序和
            sum += a;
            if(sum > maxsum){
                maxsum = sum;
                start = p;
                end = j;
            }
            if(sum < 0){      //扫到j时,前面的最大子序和是负数,那么从下一个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;
}

题解2:DP。用dp[i]表示到达第i个数时,a[1]~a[i]的最大子序和。状态转移方程为dp[i] = max(dp[i-1]+a[i], a[i])。

#include<bits/stdc++.h>
using namespace std;

int dp[100005];                    //dp[i]: 以第i个数为结尾的最大值
int main(){
    int t; cin>>t;
    for(int i=1;i<=t;i++){
        int n; cin >> n;        
        for(int j=1;j<=n;j++)  cin >> dp[j];
        int start=1, end=1, p=1;   //起点,终点,扫描位置
        int maxsum = dp[1]; 
        for(int j=2; j<=n; j++){            
            if(dp[j-1] >= 0)        //dp[i-1]大于0,则对dp[i]有贡献
                 dp[j] = dp[j-1]+dp[j];      //转移方程
            else p = j;
            if(dp[j]> maxsum ) {
                maxsum = dp[j];
                start = p;
                end = j;
            }
        }
        printf("Case %d:\n",i);
        printf("%d %d %d\n", maxsum,start,end);
        if(i != t) cout << endl;
    }
}

2. 问题(2)的求解
  和2.3.1节的滑动窗口类似,可以用单调队列的“窗口、删头、去尾”来解决问题(2)。
  首先求前缀和s[i]。s[i]是a[1]~a[i]的和,算出所有的s[i]~s[n],时间是O(n)的。
  问题(2)转换为:找出两个位置i, k,使得s[i] - s[k]最大,i - k≤ M。对于某个特定的s[i], 就是找到与它对应的最小s[k]。如果简单地暴力检查,对每个i,检查比它小的m个s[k],那么总复杂度是O(nm)的。
  用单调队列,可以使复杂度优化到O(n)。其关键是,s[k]只进入和弹出队列一次。基本过程是这样的,从头到尾检查s[],当检查到某个s[i]时,在窗口m内:
  (1)找到最小的那个s[k],并检查s[i]-s[k]是不是当前的最小子序和,如果是,就记录下来。
  (2)比s[i]大的所有s[k]都可以抛弃,因为它们在处理s[i]后面的s[i']时也用不着了,s[i']-s[i]要优于s[i']-s[k],留着s[i]就可以了。
  这个过程用单调队列最合适:s[i]进队尾时;如果原队尾比s[i]大就去尾;如果队头超过窗口范围m就去头;而最小的那个s[k]就是队头。因为每个s[i]只进出队列一次,所以复杂度为O(n)。
  下面是代码。

#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]+s[i-1];         //计算前缀和
    int ans = -1e8;
    dq.push_back(0);
    for(int i=1;i<=n;i++) {
        while(!dq.empty() && dq.front()<i-m)        //队头超过m范围:删头
            dq.pop_front();
        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])   //队尾大于s[i],去尾
            dq.pop_back();
        dq.push_back(i);
    }
    printf("%d\n",ans);
    return 0;
}

  在这个例子中,s[i]的操作实际上符合DP的特征。通过这个例子,读者能理解,为什么单调队列可以用于DP的优化。

2.4 队列习题

  (1)单调队列简单题[https://blog.csdn.net/sinat_40471574/article/details/90577147]:洛谷 P1440,P2032,P1714,P2629,P2422。

  (2)单调队列可以用于优化DP,例如多重背包的优化等。请参考:
https://blog.csdn.net/FSAHFGSADHSAKNDAS/article/details/52825227
  优化DP:洛谷 P3957、P1725。
  (3)二维队列:洛谷 P2776

3 栈

  栈的特点是“先进后出”。例如坐电梯,先进电梯的被挤在最里面,只能最后出来;一管泡腾片,最先放进管子的药片位于最底层,最后被拿出来。
  编程中常用的递归,就是用栈来实现的。栈需要用空间存储,如果栈的深度太大,或者存进栈的数组太大,那么总数会超过系统为栈分配的空间,就会爆栈,即栈溢出。这是递归的主要问题。
  本节的栈用到STL stack,或者自己写栈。为避免爆栈,需要控制栈的大小。

3.1 STL stack

  STL stack的有关操作:
  stack s;   //定义栈,Type为数据类型,如int,float,char等
  s.push(item);    //把item放到栈顶
  s.top();    //返回栈顶的元素,但不会删除。
  s.pop();    //删除栈顶的元素,但不会返回。在出栈时需要进行两步操作,先top()获得栈顶元素,再pop()删除栈顶元素
  s.size();    //返回栈中元素的个数
  s.empty();   //检查栈是否为空,如果为空返回true,否则返回false
  下面用一个例题说明栈的应用。
----------------------------------------------
hdu 1062 http://acm.hdu.edu.cn/showproblem.php?pid=1062
Text Reverse
翻转字符串。例如,输入“olleh !dlrow”,输出“hello world!”。
----------------------------------------------
  下面是hdu 1062的代码。

#include<bits/stdc++.h>
using namespace std;
int main(){
    int n;
    char ch;
    scanf("%d",&n);  getchar();
    while(n--){
        stack<char> s;
        while(true){
            ch = getchar();                   //一次读入一个字符
            if(ch==' '||ch=='\n'||ch==EOF){
                while(!s.empty()){
                    printf("%c",s.top());     //输出栈顶
                    s.pop();                  //清除栈顶
                }
                if(ch=='\n'||ch==EOF)  break;
                printf("");
            }
            else  
                s.push(ch);                   //入栈
        }
        printf("\n");
    }
    return 0;
}

3.2 手写栈

  自己写个栈,很节省空间。下面是hdu 1062的代码。

#include<bits/stdc++.h>
const int maxn = 100000 + 100;

struct mystack{
    char a[maxn];                         //存放栈元素,字符型
    int t = 0;                            //栈顶位置
    void push(char x){ a[++t] = x; }      //送入栈
    char top()       { return a[t]; }     //返回栈顶元素
    void pop()       { t--;         }     //弹出栈顶
    int empty()      { return t==0?1:0;}  //返回1表示空
}st;

int main(){
    int n;
    char ch;
    scanf("%d",&n);  getchar();
    while(n--){
        while(true){
            ch = getchar();                    //一次读入一个字符
            if(ch==' '||ch=='\n'||ch==EOF){
                while(!st.empty()){
                    printf("%c",st.top());     //输出栈顶
                    st.pop();                  //清除栈顶
                }
                if(ch=='\n'||ch==EOF)  break;
                printf("");
            }
            else  
                st.push(ch);                   //入栈
        }
        printf("\n");
    }
    return 0;
}

3.3 单调栈

  单调栈可以处理比较问题。单调栈内的元素是单调递增或递减的的,有单调递增栈、单调递减栈。
  单调栈比单调队列简单,因为栈只有一个出入口。
  下面的例题是单调栈的简单应用。
----------------------------------------------
洛谷 P2947 https://www.luogu.com.cn/problem/P2947
向右看齐
题目描述:N(1≤N≤10^5)头奶牛站成一排,奶牛i的身高是Hi(l≤Hi≤1,000,000)。现在,每只奶牛都在向右看齐。对于奶牛i,如果奶牛j满足i<j且Hi<Hj,我们说奶牛i仰望奶牛j。求出每只奶牛离她最近的仰望对象。
输入输出:第 1 行输入 N,之后每行输入一个身高 H_i。输出共 N 行,按顺序每行输出一只奶牛的最近仰望对象,如果没有仰望对象,输出 0。
输入输出样例
输入
6
3
2
6
1
1
2
输出
3
3
0
6
6
0
----------------------------------------------
题解:从后往前遍历奶牛,并用一个栈保存从低到高的奶牛,栈顶的奶牛最矮,栈底的最高。具体操作是:遍历到奶牛i时,与栈顶的奶牛比较,如果不比i高,就弹出栈顶,直到栈顶的奶牛比i高,这就是i的仰望对象;然后把i放进栈顶,栈里的奶牛仍然保持从低到高。
复杂度:每个奶牛只进出栈一次,所以是\(O(n)\)的。
  下面分别用STL stack和手写栈来实现。
(1)用STL stack实现

#include<bits/stdc++.h>
using namespace std;

int h[100001], ans[100001];
int main(){
    int n;
    scanf("%d",&n);
    for (int i=1;i<=n;i++)  scanf("%d",&h[i]);
    stack<int>st; 
    for (int i=n;i>=1;i--){
        while (!st.empty() && h[st.top()] <= h[i])  //栈顶奶牛没我高,弹出它,直到栈顶奶牛更高
            st.pop();
        if (st.empty())       //栈空,没有仰望对象
            ans[i]=0; 
        else                  //栈顶奶牛更高,是仰望对象
            ans[i]=st.top();
        st.push(i);
    }
    for (int i=1;i<=n;i++) 
        printf("%d\n",ans[i]);
    return 0;
}

(2)手写栈
  和3.2节几乎一样,只是改了栈元素的类型。

#include<bits/stdc++.h>
using namespace std;

const int maxn = 100000 + 100;
struct mystack{
    int a[maxn];                        //存放栈元素,int型
    int t = 0;                          //栈顶位置
    void push(int x){ a[++t] = x;  }    //送入栈
    int  top()      { return a[t]; }    //返回栈顶元素
    void pop()      { t--;         }    //弹出栈顶
    int empty()     { return t==0?1:0;} //返回1表示空
}st;

int h[maxn], ans[maxn];

int main(){
    int n;
    scanf("%d",&n);
    for (int i=1;i<=n;i++)  scanf("%d",&h[i]);
    for (int i=n;i>=1;i--){
        while (!st.empty() && h[st.top()] <= h[i])  //栈顶奶牛没我高,弹出它,直到栈顶奶牛更高
            st.pop();
        if (st.empty())       //栈空,没有仰望对象
            ans[i]=0; 
        else                  //栈顶奶牛更高,是仰望对象
            ans[i]=st.top();
        st.push(i);
    }
    for (int i=1;i<=n;i++) 
        printf("%d\n",ans[i]);
    return 0;
}

3.4 栈习题

  洛谷 P5788
  https://leetcode-cn.com/problemset/lcof/
    面试题09-用两个栈实现队列
    面试题30-包含min函数的栈
    面试题31-栈的压入、弹出序列
    面试题58-翻转单词顺序列(栈)

Guess you like

Origin www.cnblogs.com/luoyj/p/12409990.html
Recommended