题目:戳这里

题目大意:

给你一个数列,问能否通过两个栈的push与pop把它输出成一个升序序列(每个数只能入队并出队一次)

不能的话输出0,能的话输出操作方法

主要思路:

1.判断是否可以成功输出升序序列(二分图部分)

2.能输出的话就模拟出操作方法(贪心部分)

二分图(这里只提定义,其他应用方法可见别的题解):

把一个图的所有点分成两部分,满足每一部分内部没有连边,也就是说所有的边都在这两部分之间产生。

二分图的判定:

染色法:从一个点出发,遍历临接点,把这个点直接相连的点们标记成与该点不同的颜色(一共只有两种颜色,我们把相同颜色的点放入二分图的一个部分中);然后,如果有一个点在遍历的时候发现有一个临接点已经标记过且它的颜色和这个点相同,那么说明不符合二分图的性质,该图不是二分图。(因为这两个点一定是在同一部分(颜色一样),但是有相连的边)

以上是二分图的前置知识,下面分析此题。

  1. 因为我们只有两个栈,所以我们可以把两个栈看作二分图的两部分。
  2. 对于两个数,我们如何判断它们是否可以放在同一个栈中呢?这是本题的第一个关键点:

两个数i,j不能放入同一个栈的条件:在输入数据中存在一个位置k,满足i<j<k && a[j]>a[i]>a[k](a数组是某一个位置在输入数据中所对应的数)

解释:对于这道题来说,如果一个栈是合法的,那么一定满足栈顶的数最小且从栈顶到栈底单调递增,如果有一个比栈顶数大的数压入栈中,此时,根据栈的性质,这个较大的数一定比原来的栈顶先出栈(栈的先进后出原则),而一旦出栈,就再也回不去了,此时的输出就是不满足升序的了

所以,既然存在比a[i],a[j]都小的数a[k],而它在入栈的时候还比a[i],a[j]要晚(因为它的位置靠后),那这样的情况一定是不合法的,需要把a[i]或a[j]压入另一个栈了,因为a[j]就比a[i]位置靠后,a[i],a[j]能在一个栈中只能是在a[j]放入之前把a[i]pop掉,可是在a[k]pop之前,i是不能pop的,(a[k]比a[i]小,必须比a[i]先pop到输出队列中)所以这样一定不合法

处理方法:我们需要在合法的情况下,把所有的数全装到两个栈里,所以只要两栈都满足上述性质,这个序列就可以通过双栈排序。

我们枚举每一个数i计算比他下标大的最小数,用数组存起来,这个我们可以通过后缀来处理,复杂度O(n)

我们从1到n枚举每一个i,再枚举它后面的数j,看是否满足同栈性质,不满足的话就在这两个位置(i,j)之间建边,然后用染色法判断二分图是否合法。复杂度O(n^2^)。

预处理:

	hz[n+1]=0x7fffffff;
for(int i=n;i>=1;i--){
hz[i]=min(a[i],hz[i+1]);
}

处理二分图:(这里不一定真的要用二分图,也可以用方法2,只用到染色的思想)

方法1:建图染色

	for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
if(a[i]<a[j]&&hz[j+1]<a[i]){//刚才推导的条件,j一定在i后面,hz[j+1]一定是一个j后面的比a[j]小或等于a[j]的数
add(i,j);add(j,i);//建双向边(因为你不知道遍历的时候从谁走到谁)
}
}
}
for(int i=1;i<=n;i++){
//这里一定是要每一个点都染色才能找出图是不是二分图
if(color[i]==0){
color[i]=2;//这里初始化为2而不是3的原因是后面模拟操作的时候,栈1的操作比栈2的操作优先度高,所以我们尽量让号小多放栈1里面
dfs(i,2);
}
}
(以上是建边)
  void dfs(int u,int c){//u是当前结点,c是它的颜色
if(flag==1)return;//如果已经找到反例,直接return(flag==1表示出现两个相邻结点颜色相同)
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(color[v]==0){
color[v]=c^1;//我这里的颜色用的2和3来标记,所以^运算就可以了
dfs(v,c^1);
}else{
if(color[v]==color[u]){
flag=1;
return;
}
}
}
}

方法2:直接染色判断

	for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
if(a[i]<a[j]&&hz[j+1]<a[i]){
if(!color[i]&&color[j])color[i]=color[j]^1;
else if(color[i]&&!color[j])color[j]=color[i]^1;
else if(!color[i]&&!color[j])color[i]=2,color[j]=3;
else{
if(color[i]==color[j])
flag=1;
}
}
//与dfs类似的染色判断
}
}
for(int i=1;i<=n;i++){
if(color[i]==0){
color[i]=2;
//这里记得把所有的没染色的点都染上色
}
}

然后我们只需要判断一下flag的状态就可以知道这个序列是否可以“双栈排序”了。

接下来我们已经得知此序列可不可以排序了,那么我们就可以通过贪心模拟的方法模拟栈操作的全过程来输出操作方法。

关于这道题的输出顺序,大家要注意这是输出字典序,而且栈1的操作a,b 栈2的操作c,d。

所以我们可以推导出一下几点:

1.当两栈都可以pop的时候,优先看栈1能不能入(a),再看栈1能不能出(b),再看栈2能不能入(c),再看栈2能不能出(d)。

2.当一个栈可以pop的时候,一定是目前想要放进去的这个元素比栈顶元素数值大,那其实这个时候是不能入栈的,所以每次可以pop的时候,我们只考虑(b、d操作即可)

(以上一片废话)

看代码吧,注释更清楚些:

int now=1;//now标记目前应该从栈中pop到输出序列的那个值
for(int i=1;i<=n;i++){
//color=2表示在栈1,color=3表示在栈2
if(color[i]==2){
while(!q1.empty()&&q1.top()<t[i]){//如果目前这个数比栈首大,执行pop操作
while(!q1.empty()&&q1.top()==now){
printf("b ");now++;
q1.pop();
}
//这里没有栈2的pop操作是因为:有可能接下来的这个数可以push进栈1中,它的优先度更高
//我们一定是先把栈1能push/pop的全处理完再考虑栈2的pop
}
}else{
while(!q2.empty()&&q2.top()<t[i]){
while(q2.empty()&&q2.top()==now){
printf("d ");now++;
q2.pop();
}
//这里跟上面不一样,因为栈1的pop操作优先度比栈2的push高,所以每pop完栈2,都要考虑能否pop栈1
while(!q1.empty()&&q1.top()==now){
printf("b ");now++;
q1.pop();
}
}
}
//有可能在上面的操作中,栈2不满足pop条件,所以没进上面的循环,但是栈1可能满足pop条件,优先pop它再push栈2。
if(color[i]==3){
while(!q1.empty()&&q1.top()==now){
printf("b ");now++;q1.pop();
}
}
if(color[i]==2){
q1.push(t[i]);printf("a ");
}else{
q2.push(t[i]);printf("c ");
}
}

之后记得按先栈1再栈2的顺序把栈清空。

全部代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+10,maxm=1e6+10;
struct E{
int to,next;
}edge[maxm];
int head[maxn],tot;
void add(int from,int to){
edge[++tot].to=to;
edge[tot].next=head[from];
head[from]=tot;
}
int n,t[maxn],dp[maxn],color[maxn],flag;
int sta[maxm],top,sta2[maxm],top2;
void dfs(int u,int c){
if(flag==1)return;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(color[v]==0){
color[v]=c^1;
dfs(v,c^1);
}else{
if(color[v]==color[u]){
flag=1;
return;
}
}
}
}
stack<int> q1,q2;
int main(){
scanf("%d",&n);for(int i=1;i<=n;i++)scanf("%d",&t[i]);
dp[n+1]=0x7fffffff;
for(int i=n;i>=1;i--){
dp[i]=min(t[i],dp[i+1]);
}
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
if(t[i]<t[j]&&dp[j+1]<t[i]){
add(i,j);add(j,i);
}
}
}
for(int i=1;i<=n;i++){
if(color[i]==0){
color[i]=2;
dfs(i,2);
}
}
if(flag==1){
printf("0\n");
return 0;
}
int now=1;
for(int i=1;i<=n;i++){
if(color[i]==2){
while(!q1.empty()&&q1.top()<t[i]){
while(!q1.empty()&&q1.top()==now){
printf("b ");now++;
q1.pop();
}
}
}else{
while(!q2.empty()&&q2.top()<t[i]){
while(q2.empty()&&q2.top()==now){
printf("d ");now++;
q2.pop();
}
while(!q1.empty()&&q1.top()==now){
printf("b ");now++;
q1.pop();
}
}
}
if(color[i]==3){
while(!q1.empty()&&q1.top()==now){
printf("b ");now++;q1.pop();
}
}
if(color[i]==2){
q1.push(t[i]);printf("a ");
}else{
q2.push(t[i]);printf("c ");
}
}
int flag=1;
while(flag){
flag=0;
while(!q1.empty()&&q1.top()==now){
printf("b ");q1.pop();now++;
flag=1;
}
while(!q2.empty()&&q2.top()==now){
printf("d ");q2.pop();now++;
flag=1;
}
}
return 0;
}

最新文章

  1. C#关于分页显示
  2. CentOS上安装man手册
  3. C++ Daily 《5》----虚函数表的共享问题
  4. Chapter 2: Design the user experience
  5. Js根据Ip地址自动判断是哪个城市
  6. JSONP实现跨域
  7. 关于SQLSERVER联合查询一点看法
  8. c#带参数和返回值的函数 开启线程调用的方法
  9. IOI1998 hdu1828 poj1177 Picture
  10. Android adapter适配器的使用
  11. HTML css面试题
  12. jQuery的拾色器
  13. 如何通过android代码获取LTE信息?
  14. 译MassTransit 快速入门
  15. Win10 将slim加入PYTHONPYTH
  16. 语法设计——基于LL(1)文法的预测分析表法
  17. 第六篇-以隐式意图(Implicit Intent)呼叫系统服务
  18. mysql 查看某个数据库中所有表的数据量
  19. The packaging and installation process of Android programs
  20. AudioEffect中文API

热门文章

  1. Java使用JDBC连接Oracle数据库
  2. HashSet保证元素唯一原理以及HashMap扩容机制
  3. Unity接入多个SDK的通用接口开发与资源管理(三)
  4. liunx配置本地yum源和更新aliyun yum源
  5. oracle之三资源管理
  6. [Leetcode]148. 排序链表(归并排序)
  7. vue简单案例_动态添加删除用户数据
  8. 容器云平台No.6~企业级分布式存储Ceph
  9. Sorted Adjacent Differences(CodeForces - 1339B)【思维+贪心】
  10. MATLAB 安装