双栈排序(洛谷P1155)二分图的判定+思维贪心
题目:戳这里
题目大意:
给你一个数列,问能否通过两个栈的push与pop把它输出成一个升序序列(每个数只能入队并出队一次)
不能的话输出0,能的话输出操作方法
主要思路:
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;
}
双栈排序(洛谷P1155)二分图的判定+思维贪心的更多相关文章
- AC日记——双栈排序 洛谷 P1155
双栈排序 思路: 二分图染+模拟: 代码: #include <bits/stdc++.h> using namespace std; #define maxn 1005 #define ...
- 洛谷P1155 双栈排序题解(图论模型转换+二分图染色+栈)
洛谷P1155 双栈排序题解(图论模型转换+二分图染色+栈) 标签:题解 阅读体验:https://zybuluo.com/Junlier/note/1311990 原题地址:洛谷P1155 双栈排序 ...
- [NOIP2008] 提高组 洛谷P1155 双栈排序
题目描述 Tom最近在研究一个有趣的排序问题.如图所示,通过2个栈S1和S2,Tom希望借助以下4种操作实现将输入序列升序排序. 操作a 如果输入序列不为空,将第一个元素压入栈S1 操作b 如果栈S1 ...
- 洛谷——P1155 双栈排序
题目描述 Tom最近在研究一个有趣的排序问题.如图所示,通过2个栈S1和S2,Tom希望借助以下4种操作实现将输入序列升序排序. 操作a 如果输入序列不为空,将第一个元素压入栈S1 操作b 如果栈S1 ...
- P1155 双栈排序(二分图染色)
P1155 双栈排序(二分图染色) 题目描述 Tom最近在研究一个有趣的排序问题.如图所示,通过2个栈S1和S2,Tom希望借助以下4种操作实现将输入序列升序排序. 操作a 如果输入序列不为空,将第一 ...
- [LuoguP1155]双栈排序_二分图_bfs
双栈排序 题目链接:https://www.luogu.org/problem/P1155 数据范围:略. 题解: 神仙题. 就第一步就够劝退了. 这个二分图非常不容易,首先只有两个栈,不是属于一个就 ...
- NOIP2008双栈排序[二分图染色|栈|DP]
题目描述 Tom最近在研究一个有趣的排序问题.如图所示,通过2个栈S1和S2,Tom希望借助以下4种操作实现将输入序列升序排序. 操作a 如果输入序列不为空,将第一个元素压入栈S1 操作b 如果栈S1 ...
- P1155 双栈排序
题目描述 Tom最近在研究一个有趣的排序问题.如图所示,通过2个栈S1和S2,Tom希望借助以下4种操作实现将输入序列升序排序. 操作aaa 如果输入序列不为空,将第一个元素压入栈S1 操作b 如果 ...
- 二分图 and code1170 双栈排序
6.6二分图 二分图是这样一个图: 有两顶点集且图中每条边的的两个顶点分别位于两个顶点集中,每个顶点集中没有边直接相连接. 无向图G为二分图的充分必要条件是,G至少有两个顶点,且其所有回路的长度均为偶 ...
随机推荐
- python日志模块配置
import logging logging.basicConfig(filename= 'out.log',filemode= 'w+', level= logging.DEBUG, format= ...
- d3力导图绘制节点间多条关系平行线的方法
之前用d3做了多条线之间的绘图是曲线表示的,现在产品要求改成平行线的样式,经过在网上的调研和自己的尝试,实践出一个可用的方法,分享给大家,先展示下结果: 事先声明,本方法是在以下参考网站上进行的结合和 ...
- FTL指令常用标签及语法
FTL指令常用标签及语法注意:使用freemaker,要求所有标签必须闭合,否则会导致freemaker无法解析. freemaker注释:<#-- 注释内容 -->格式部分,不会输出 - ...
- dubbo学习(五)注册中心zookeeper
初识zookeeper 下载地址:https://archive.apache.org/dist/zookeeper/ 详细的ZooKeeper教程戳这里~ PS: 建议目前选择3.4的稳定版本进行使 ...
- 《Netty权威指南》笔记
第1章 Java的I/O演进之路 1.1 Linux网络I/O模型 fd:file descriptor,文件描述符.linux内核将所有外部设备都看作一个文件来操作,对文件的读写会调用内核提供的命令 ...
- Apollo系列(二):Apollo在ASP.NET Core 3.1中使用
关于Apollo怎么安装,我就不介绍,可以看这篇文章:https://www.cnblogs.com/vic-tory/p/13736192.html 一.Apollo使用: 1.创建项目 2.添加配 ...
- GZip 压缩解压 工具类 [ GZipUtil ]
片段 1 片段 2 pom.xml <dependency> <groupId>commons-codec</groupId> <artifactId> ...
- spring cloud consul 服务治理
对照系统安装响应consul文件(以window为例) 解压文件之后配置环境,进入Path添加文件所在目录, 测试:在文件所在目录下进入指令操作 输入 consul agent -dev 启动成功,在 ...
- Go-变量-var
什么是变量? 一种抽象,计算机用来保存现实数据的容器,通过这个变量抽象可以写入现实数据到计算机中,并且可以读取变量取到保存到计算机中的现实数字化数据 Go-变量定义 关键字 var 关键符号 := i ...
- 重拾H5小游戏之入门篇(二)
上一篇,水了近千字,很酸爽,同时表达了"重拾"一项旧本领并不容易,还有点题之效果.其实压缩起来就一句话:经过了一番记忆搜索,以及try..catch的尝试后,终于选定了Phaser ...