第三周练习主要涉及栈、队列、STL
本周训练主要是题目,知识点的讲解较少。
拓展:最大权闭合图
栈作为一种数据结构被广泛使用,基本性质为先进后出。
例题1(HDU 1022)
思路:通过比较两个序列所对应的元素来判断
证明:首先我们可以知道,第二个序列为出栈序列,那么问题就是如何利用栈进行操作实现每一次的栈顶都符合右边出栈序列的对应元素,对于两个序列的元素,设置两个迭代器L,R。当L≠R,代表当R弹出时,L不为栈顶,此时需要查找下一个L,记录操作入栈,直到找到第一个与R相同的L,代表此时的L为应该出栈的栈顶元素,记录操作出栈,查找下一个R,如此反复
代码
#include
#include
#include
#include
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;
}
思路:在线处理,根据每次录入的符号直接处理相关数据
代码
#include
#include
#include
#include
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
#include
#include
#include
#include
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;
}
题目
题目大意:给定数值为1~n的纸牌以及各自的翻转状态,每次操作将最左或最右边的一堆纸牌通过翻转放置在次左或次右上,最后查询给定纸牌的状态。
思路:开辟n个栈,每个栈存放纸牌,再开辟n个栈存储翻转状态,每次合并两个栈的元素
代码
#include
#include
#include
#include
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;
}
题目
题目大意:给一系列相连的矩形块,求出其构成的最大的相连无空隙的矩形的面积
思路:对每个矩形块找出可拓展的最左端和最右端并记录,对每个矩形块来说可以使用先前的已经求出的结果,最后计算以每个矩形块为中心所构成矩形的最大值
代码
#include
#include
#include
#include
#include
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
#include
#include
#include
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;
}
队列基本特征,先进先出。
思路:
代码
题目
题目大意:团体队列,首先给出哪些元素为一组,对应每一个元素,如果在队列中它已经有同组元素在其中,则它直接插入在其同组队友之后,否则就加在最后,对每次操作输出对应结果
思路:解题过程中参考了CSDN上的其他代码。以队为单元入整个队列,之后的元素入队列块。
代码
#include
#include
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
#include
#include
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;
}
STL是C++的一大特性,它实现了数据结构与算法的分离,并且其性质不为面向对象。
本篇收录一些常用的适配器与容器的相关用法。
相关的较为详细用法参考
《C++STL基础及应用(第2版)》——清华大学出版社
实现原理参考
《STL源码解析》 (计划等到阅读完《C++ Primer》之后细读)
map/unordered_map
unordered_map的底层采用哈希表的实现,查询的时间复杂度为O(1)。unordered_map内部是无序的,属于关联式容器,采用std::pair保存key-value形式的数据。用法与map一致。
unoredered_map使用不需要比较元素的key值的大小。
如果需要对map中的数据排序,就首选map,其会把数据按照key的自然排序排序,如果不需要排序,一般都会选择unordered_map,它的查找效率会更高。
unordered_map在上一篇已经提及。
set/multiset
std::set 是关联容器,含有 Key 类型对象的已排序集。用比较函数compare进行排序。搜索、移除和插入拥有O(logN)复杂度。
set容器内的元素会被自动排序,set与map不同,set中的元素即是键值又是实值,set不允许两个元素有相同的键值。不能通过set的迭代器去修改set元素。
由于set元素是排好序的,且默认为升序,因此当set集合中的元素为结构体或自定义类时,该结构体或自定义类必须实现运算符‘<’的重载。
multiset特性及用法和set完全相同,唯一的差别在于它允许键值重复。
set和multiset的底层实现是一种高效的平衡二叉树,即红黑树。
例题(HDU 1276)
题目大意:每一次对序列中2的倍数和3的倍数进行剔除,之后向同序号小的方向收缩,循环,直到剩下元素个数不超过3个
思路:直接模拟该过程,用链表应该能加快速度,但是数组每次遍历也可以AC,数据规模比较小
代码
#include
#include
#include
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;
}