ACM Weekly 3(待修改)

本文深入探讨了栈、队列及其在ACM竞赛中的应用,包括单调队列和STL容器的使用。通过实例解析了HDU1022、1237、3328、1506等题目,涉及最大权闭合图、简易计算器、纸牌翻转、矩形面积计算等挑战。同时,介绍了如何使用STL简化代码,如自定义栈和单调队列。文章还提供了HDU4122、1387等题目的解题思路,展示了队列操作和数据结构的巧妙运用。
摘要由CSDN通过智能技术生成

涉及的知识点

第三周练习主要涉及栈、队列、STL
本周训练主要是题目,知识点的讲解较少。
拓展:最大权闭合图

栈作为一种数据结构被广泛使用,基本性质为先进后出。

HDU 1022

在这里插入图片描述
题目大意:给出两个序列A、B,判断是否能通过栈使得A->B

思路:通过比较两个序列所对应的元素来判断

证明:首先我们可以知道,第二个序列为出栈序列,那么问题就是如何利用栈进行操作实现每一次的栈顶都符合右边出栈序列的对应元素,对于两个序列的元素,设置两个迭代器L,R。当L≠R,代表当R弹出时,L不为栈顶,此时需要查找下一个L,记录操作入栈,直到找到第一个与R相同的L,代表此时的L为应该出栈的栈顶元素,记录操作出栈,查找下一个R,如此反复

代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
using namespace std;
char Stack[20000];//自定义栈
bool operation[20000];//记录操作
char in[20000],out[20000];
int n,ans=-1,outacc,flag;
int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        memset(in,0,sizeof(in));
        memset(out,0,sizeof(out));
        memset(Stack,0,sizeof(Stack));
        memset(operation,0,sizeof(operation));//初始化
        scanf("%s",in);
        scanf("%s",out);
        ans=-1;
        outacc=0;
        flag=0;
        for(int i=0; i<n; i++)
        {
            Stack[++ans]=in[i];
            operation[flag++]=true;
            while(ans!=-1&&Stack[ans]==out[outacc])//进行序列的逐一比对
            {
                ans--;
                operation[flag++]=false;
                outacc++;
            }
        }
        if(outacc==n)//判断是否能得到
        {
            printf("Yes.\n");
            for(int i=0; i<flag; i++)
                if(operation[i])
                    printf("in\n");
                else
                    printf("out\n");
        }
        else
            printf("No.\n");
        printf("FINISH\n");
    }
    return 0;
}

HDU 1237

在这里插入图片描述
题目大意:简易计算器

思路:在线处理,根据每次录入的符号直接处理相关数据

代码

#include <iostream>
#include <ctype.h>
#include <cstring>
#include <cstdlib>
using namespace std;
double num[250];//数字栈
int ans,first;
bool flag;
int main()
{
    while(scanf("%d",&first))
    {
        char ch=getchar();
        ans=0;
        num[ans++]=(double)first;
        char character,tmp='\0';
        double data;
        if(first==0&&ch=='\n')//这个地方判断是否单独为0
            break;
        while(scanf("%c %lf%c",&character,&data,&tmp)!=EOF)
        {
            switch(character)
            {
            case '+':
                num[ans++]=data;//数字直接录入
                break;
            case '-':
                num[ans++]=-data;//同上
                break;
            case '*':
                num[ans-1]*=data;//在线处理,直接计算并入栈
                break;
            case '/':
                num[ans-1]/=data;
            }
            if(tmp!=' ')
                break;
        }
        double sum=0.0f;
        for(int i=0; i<ans; i++)
            sum+=num[i];//统计栈中元素和
        printf("%.2lf\n",sum);
    }
    return 0;
}

使用STL的写法

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <stack>
using namespace std;
bool operation[20000];//记录操作
char in[20000],out[20000];
int n,outacc,flag;
int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        memset(in,0,sizeof(in));
        memset(out,0,sizeof(out));
        stack<int>S;
        memset(operation,0,sizeof(operation));//初始化
        scanf("%s",in);
        scanf("%s",out);
        outacc=0;
        flag=0;
        for(int i=0; i<n; i++)
        {
            S.push(in[i]);
            operation[flag++]=true;
            while(!S.empty()&&S.top()==out[outacc])//进行序列的逐一比对
            {
                S.pop();
                operation[flag++]=false;
                outacc++;
            }
        }
        if(outacc==n)//判断是否能得到
        {
            printf("Yes.\n");
            for(int i=0; i<flag; i++)
                if(operation[i])
                    printf("in\n");
                else
                    printf("out\n");
        }
        else
            printf("No.\n");
        printf("FINISH\n");
    }
    return 0;
}

HDU 3328

在这里插入图片描述
题目大意:给定数值为1~n的纸牌以及各自的翻转状态,每次操作将最左或最右边的一堆纸牌通过翻转放置在次左或次右上,最后查询给定纸牌的状态。

思路:开辟n个栈,每个栈存放纸牌,再开辟n个栈存储翻转状态,每次合并两个栈的元素

代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
using namespace std;
int num[120][120],low,high,n,ans;//模拟栈
bool type[120][120];//判断翻转状态
int main()
{
    //freopen("test.txt","r",stdin);
    while(scanf("%d",&n)&&n)
    {
        high=n;
        low=1;
        getchar();
        for(int i=1; i<=n; i++)
        {
            num[i][1]=i;//第一个元素入栈
            num[i][0]++;//第一个位置用来记录个数
            char tmp;
            scanf("%c",&tmp);
            if(tmp=='U')
                type[i][1]=true;
        }
        getchar();
        for(int i=0;i<n-1;i++)
        {
            char order;
            scanf("%c",&order);
            if(order=='R')
            {
                int L1=num[high-1][0],L2=num[high][0];
                for(int j=L1+1,k=L2;k>0;j++,k--)
                {
                    num[high-1][j]=num[high][k];//入栈
                    num[high-1][0]++;//计数
                    type[high-1][j]=type[high][k]^1;//翻转
                }
                high--;
            }
            else
            {
                int L1=num[low+1][0],L2=num[low][0];
                for(int j=L1+1,k=L2;k>0;j++,k--)
                {
                    num[low+1][j]=num[low][k];
                    num[low+1][0]++;
                    type[low+1][j]=type[low][k]^1;
                }
                low++;
            }
        }
        int seeknum=0;
        printf("Pile %d\n",++ans);
        scanf("%d",&seeknum);
        for(int i=0;i<seeknum;i++)
        {
            int t=0;
            scanf("%d",&t);
            printf("Card %d is a face ",t);
            if(type[high][n-t+1])//判断状态
                printf("up ");
            else
                printf("down ");
            printf("%d.\n",num[high][n-t+1]);//输出元素
        }
        memset(type,0,sizeof(type));//清空
        memset(num,0,sizeof(num));
    }
    return 0;
}

HDU 1506

在这里插入图片描述
题目大意:给一系列相连的矩形块,求出其构成的最大的相连无空隙的矩形的面积

思路:对每个矩形块找出可拓展的最左端和最右端并记录,对每个矩形块来说可以使用先前的已经求出的结果,最后计算以每个矩形块为中心所构成矩形的最大值

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
using namespace std;
typedef long long ll;
ll Height[121212],area,L[121212],R[121212];//分别存储高度、最左端大于等于该位置高度的位置、最右端大于等于该位置高度的位置
int N;
int main()
{
    while(scanf("%d",&N)&&N)
    {
        for(int i=1; i<=N; i++)
            scanf("%lld",&Height[i]);
        L[1]=1;
        R[N]=N;
        for(int i=2; i<=N; i++)
        {
            int t=i;
            while(t>1&&Height[t-1]>=Height[i])//寻找最左端,如果该位置的左端可以被拓展,那么,该位置的左端的最左端也能被拓展,以此类推
                t=L[t-1];
            L[i]=t;
        }
        for(int i=N-1; i>=1; i--)
        {
            int t=i;
            while(t<N&&Height[t+1]>=Height[i])//同上
                t=R[t+1];
            R[i]=t;
        }
        for(int i=1; i<=N; i++)
            area=max((R[i]-L[i]+1)*Height[i],area);//遍历,查找最大值
        printf("%lld\n",area);
        memset(Height,0,sizeof(Height));//清空
        memset(L,0,sizeof(L));
        memset(R,0,sizeof(R));
        area=0;
    }
    return 0;
}

本题还有另外的方法:单调栈

思路:维持一个单调栈,遇到小于栈顶的待测元素,进行弹出,每次弹出统计已经弹出的数量作为弹出元素最多可构造的面积的长,并且遇到第一个小于等于待测元素的栈内元素,待测元素入栈,此时待测元素的可构造面积为先前弹出的数量(都大于它)加上1(它自己)的和乘以它的高度

代码

#include <iostream>
#include <stack>
#include <cstdlib>
#include <cstring>
using namespace std;
typedef long long ll;
int n,area[121212];//保存每个位置的可构造的最大面积
ll ans;
int main()
{
    scanf("%d",&n);
    while(n)
    {
        stack<int>S;
        memset(area,0,sizeof(area));//清空
        ans=0;
        n++;
        while(n--)
        {
            int t=0;
            if(n)
                scanf("%d",&t);
            if(S.empty()||S.top()<t)//如果栈空或者待检测元素大于栈顶,入栈
            {
                S.push(t);
                area[S.size()]=1;//默认初始值为1,即只用自己构建
            }
            else//如果待检测元素小于等于栈顶,出栈
            {
                int acc=0;
                while(!S.empty()&&S.top()>t)//找到第一个小于等于待测元素的已入栈元素
                {
                    acc+=area[S.size()];
                    /*记录出栈元素的构造面积,这个构造面积是相对于第一个小于等于待测元素的已入栈元素,
                    因为待测元素比它小,破坏了它的面积的连续*/
                    ans=max(ans,(ll)S.top()*acc);//ans记录最大值,top栈顶元素高度×acc已经出栈的个数(出栈都比它大)
                    S.pop();
                }
                S.push(t);
                area[S.size()]=acc+1;
                /*栈顶元素对应的面积发生了变化,栈顶元素为原待测元素,出栈的元素都大于它,
                所以面积加上acc,然后还要加上它自己,所以面积+1*/
            }
        }
        printf("%lld\n",ans);
        scanf("%d",&n);
    }
    return 0;
}

队列

队列基本特征,先进先出。

HDU 4122

在这里插入图片描述
题目大意:以2000年1月1日0时为计时点,给出N个订单的年月日时与数目,开业时间为0点到M-1点,给出每个整点做一个月饼的花费,给出月饼的保质期与每个月饼的存储消耗,求满足订单要求的最小花费,订单可能同时到达

思路:本题题意需要理解清楚,前面一堆废话。使用单调队列来动态存储区间最值,存储的对象为时刻+该时刻做月饼的单位消耗,对于订单,只需要考虑一个月饼的最值即可,对于一个月饼,权衡点在这个时刻做不做,详见代码与注释

代码

#include <iostream>
#include <deque>
#include <cstdio>
#include <cstdlib>
#include <unordered_map>
using namespace std;
typedef long long ll;
typedef pair<ll,ll>PR;
deque<PR>high_queue;//单调队列载体,记录时刻与以当前时刻制作的单位消耗
unordered_map<string,ll>connect;//将月份和数字联系
ll N,M,Day,Year,H,R,T,S,t;
PR Order[2502];//记录订单
string Month;
void Init()
{
    connect["Jan"]=1,connect["Feb"]=2,connect["Mar"]=3,connect["Apr"]=4,connect["May"]=5,connect["Jun"]=6,connect["Jul"]=7,connect["Aug"]=8;
    connect["Sep"]=9,connect["Oct"]=10,connect["Nov"]=11,connect["Dec"]=12;
}
ll getDay(ll year,ll month,ll day)//获得对应订单对应哪一天
{
    ll ans=0;
    ans=(year-1)*365+((year-1)/4-(year-1)/100+(year-1)/400);//计算已过年的天数
    for(ll i=1; i<month; i++)
    {
        if(i==2) ans+=28+(((year%4==0&&year%100!=0)||year%400==0)?1:0);
        else if(i==4||i==6||i==9||i==11)ans+=30;
        else ans+=31;
    }
    return ans+day;
}
ll getHour(string month,ll day,ll year,ll hour)//计算小时
{
    ll ans=getDay(year,connect[month],day)-getDay(2000,1,0)-1;//减去1,这一天没玩
    ans*=24;
    return ans+hour;
}
int main()
{
    Init();
    while(~scanf("%lld%lld",&N,&M)&&N&&M)
    {
        high_queue.clear();
        ll sum=0;
        for(int i=1; i<=N; i++)
        {
            cin >>Month;
            scanf("%lld%lld%lld%lld",&Day,&Year,&H,&R);
            Order[i].first=getHour(Month,Day,Year,H);//记录相对于原点的时间
            Order[i].second=R;//记录月饼数量
        }
        scanf("%lld%lld",&T,&S);
        ll j=1;
        for(int i=0; i<M; i++)
        {
            scanf("%lld",&t);
            while(!high_queue.empty()&&(high_queue.back().second+(i-high_queue.back().first)*S)>=t)high_queue.pop_back();
            //如果单调队列不为空并且以末尾时刻制作一个月饼存放到i时刻的花费大于在i时刻直接做一个,则去掉末尾值(这里是以一个月饼为单位而考虑的)
            high_queue.push_back(make_pair(i,t));
            while(Order[j].first==i)//如果订单到达
            {
                while(!high_queue.empty()&&high_queue.front().first+T<i)high_queue.pop_front();//判断单调队列存储的解是否超过了保质期
                sum+=((i-high_queue.front().first)*S+high_queue.front().second)*Order[j++].second;//那么就在这个时刻做月饼
            }
        }
        printf("%lld\n",sum);
    }
    return 0;
}

HDU 1387

在这里插入图片描述
题目大意:团体队列,首先给出哪些元素为一组,对应每一个元素,如果在队列中它已经有同组元素在其中,则它直接插入在其同组队友之后,否则就加在最后,对每次操作输出对应结果

思路:解题过程中参考了CSDN上的其他代码。以队为单元入整个队列,之后的元素入队列块。
在这里插入图片描述

代码

#include <iostream>
#include <cstring>
using namespace std;
typedef struct node
{
    int data;
    struct node*next;
    node (int &x)
    {
        next=NULL;
        data=x;
    }
} node;
class Deque
{
private:
    node*first,*last;
public :
    Deque()
    {
        first=last=NULL;
    }
    void Push(int &x)
    {
        node*tmp=new node(x);
        if(!first)
        {
            first=tmp;
            last=tmp;
        }
        else
        {
            last->next=tmp;
            last=tmp;
        }
    }
    void Pop()
    {
        node*tmp=first;
        first=first->next;
        delete tmp;
    }
    int Front()
    {
        return first->data;
    }
    bool Empty()
    {
        return !first;
    }
    ~Deque()
    {
        node*tmp=first;
        while(first)
        {
            tmp=first;
            first=first->next;
            delete tmp;
        }
    }
};//自定义队列类
int n,ans;
int team[1000002];//因为本周禁用STL,所以直接以空间换时间
//203 103 202 102 101 201
int main()
{
    //freopen("test.txt","r",stdin);
    while(scanf("%d",&n)&&n)
    {
        Deque que,que_array[1010];//前者记录入队的队伍,后者记录队列中每个队伍的元素先后
        printf("Scenario #%d\n",++ans);
        for(int i=1; i<=n; i++)
        {
            int num;
            scanf("%d",&num);
            for(int j=0; j<num; j++)
            {
                int t;
                scanf("%d",&t);
                team[t]=i;//记录每个元素的队伍
            }
        }
        char order[20];
        while(1)
        {
            scanf("%s",order);
            if(order[0]=='E')
            {
                int t;
                scanf("%d",&t);
                if(que_array[team[t]].Empty())//如果该队伍不在队中,则将队伍号入队
                    que.Push(team[t]);
                que_array[team[t]].Push(t);//将对应元素入相应队伍
            }
            else if(order[0]=='D')
            {
                int t=que.Front();
                printf("%d\n",que_array[t].Front());
                que_array[t].Pop();
                if(que_array[t].Empty())//如果该队伍已经为空,那么将队伍号从队列中弹出
                    que.Pop();
            }
            else
                break;
        }
        putchar('\n');
        memset(team,0,sizeof(team));//清空
    }
    return 0;
}

如果使用了STL便可以大幅简化代码

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
int n,ans;
int team[1000002];//可用unordered_map代替
int main()
{
    while(scanf("%d",&n)&&n)
    {
        queue<int> que,que_array[1010];
        printf("Scenario #%d\n",++ans);
        for(int i=1; i<=n; i++)
        {
            int num;
            scanf("%d",&num);
            for(int j=0; j<num; j++)
            {
                int t;
                scanf("%d",&t);
                team[t]=i;
            }
        }
        char order[20];
        while(1)
        {
            scanf("%s",order);
            if(order[0]=='E')
            {
                int t;
                scanf("%d",&t);
                if(que_array[team[t]].empty())
                    que.push(team[t]);
                que_array[team[t]].push(t);
            }
            else if(order[0]=='D')
            {
                int t=que.front();
                printf("%d\n",que_array[t].front());
                que_array[t].pop();
                if(que_array[t].empty())
                    que.pop();
            }
            else
                break;
        }
        putchar('\n');
        memset(team,0,sizeof(team));
    }
    return 0;
}

单调队列

单调队列与普通队列不同,其内容是递增或递减的,即有序的,一般用来动态查找,如求出一个区间内的最大值与最小值,它的操作与队列一样,不过是分时机才使用入队与出队

单调队列一般采用双端队列来实现,以求区间最大值为例,给定一个容量大小K,这在单调队列里一般称为窗,对扫描的值进行入队与出队

为保证单调队列的有序(这里是降序),在插入元素时,将队尾的元素和插入元素比较,如果队尾的元素不大于插入元素,则删除队尾的元素,然后继续将新的队尾的元素与插入元素比较,直到队尾的元素大于插入元素,插入

上述为队尾元素的插入与删除,对于队首元素来说,只有删除这一操作,因为容量是固定的,所以我们只需要将不属于范围的队首元素去除即可,更详细的讲解见参考文献

STL

STL是C++的一大特性,它实现了数据结构与算法的分离,并且其性质不为面向对象。
本篇收录一些常用的适配器与容器的相关用法。
相关的较为详细用法参考
《C++STL基础及应用(第2版)》——清华大学出版社
实现原理参考
《STL源码解析》 (计划等到阅读完《C++ Primer》之后细读)

  1. stack//栈
    栈的基本操作包括出入栈、判空、容量等等。在这里插入图片描述
    在这里插入图片描述

  2. queue//队列
    队列基本操作为出入队
    在这里插入图片描述
    在这里插入图片描述

  3. map/unordered_map
    unordered_map的底层采用哈希表的实现,查询的时间复杂度为O(1)。unordered_map内部是无序的,属于关联式容器,采用std::pair保存key-value形式的数据。用法与map一致。
    unoredered_map使用不需要比较元素的key值的大小。
    如果需要对map中的数据排序,就首选map,其会把数据按照key的自然排序排序,如果不需要排序,一般都会选择unordered_map,它的查找效率会更高。
    unordered_map在上一篇已经提及。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  4. set/multiset
    std::set 是关联容器,含有 Key 类型对象的已排序集。用比较函数compare进行排序。搜索、移除和插入拥有O(logN)复杂度。
    set容器内的元素会被自动排序,set与map不同,set中的元素即是键值又是实值,set不允许两个元素有相同的键值。不能通过set的迭代器去修改set元素。
    由于set元素是排好序的,且默认为升序,因此当set集合中的元素为结构体或自定义类时,该结构体或自定义类必须实现运算符‘<’的重载。
    multiset特性及用法和set完全相同,唯一的差别在于它允许键值重复。
    set和multiset的底层实现是一种高效的平衡二叉树,即红黑树。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  5. priority_queue
    优先队列,即为堆
    在这里插入图片描述
    在这里插入图片描述

  6. heap
    真正意义上的堆是在容器的基础上构造的,属于算法库
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

难题解析

HDU 1276

在这里插入图片描述
题目大意:每一次对序列中2的倍数和3的倍数进行剔除,之后向同序号小的方向收缩,循环,直到剩下元素个数不超过3个

思路:直接模拟该过程,用链表应该能加快速度,但是数组每次遍历也可以AC,数据规模比较小

代码

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
bool judge[5050];//模拟链表
int N;
int main()
{
    cin >>N;
    while(N--)
    {
        memset(judge,0,sizeof(judge));//清空
        int num=0,n=0,ans=0,k=2;
        cin >>num;
        n=num;
        while(num>3)
        {
            int acc=0;
            for(int i=1; i<=n; i++)
            {
                if(!judge[i])
                {
                    acc++;
                    if(acc==k)
                    {
                        judge[i]=true;
                        acc=0;
                        num--;
                    }
                }
            }
            k=((k==2)?3:2);
        }
        for(int i=1; i<=n; i++)
        {
            if(!judge[i]&&i==1)
                cout <<"1";
            else if(!judge[i])
                cout <<" "<<i;
        }
        cout <<endl;
    }
    return 0;
}

拓展知识点

最大权闭合图//本题需要学习网络流,暂时搁置

HDU 3061

在这里插入图片描述
题目大意:
思路:
代码

参考文献

  1. HDU-4122 Alice’s mooncake shop 单调队列
  2. 《C++STL基础及应用(第2版)》——清华大学出版社
  3. 单调队列初步
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值