双栈排序(洛谷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至少有两个顶点,且其所有回路的长度均为偶 ...
随机推荐
- Java中String.strip()和String.trim()方法
strip和trim String.trim() 可以去除字符串前后的"半角"空白字符 String.strip() 可以去除字符串前后的"全角和半角"空白字符 ...
- java线程的3种实现方式及线程池
1 准备数据 1.1 目标 为了形象地演示线程的工作现象, 准备两个文件datas/odds.txt和datas/evens.txt, 分别存储奇数和偶数, 内容如下: odds.txt 1 3 5 ...
- Playbook使用,编写YAML
YAML是什么? YAML是一个可读性高.用来表达数据序列的格式语言 YAML:YAML Ain't a Markup Language YAML以数据为中心,重点描述数据的关系和结构 YAML的格式 ...
- Redis之命令详解
Redis命令手册:http://doc.redisfans.com/
- VC 编译 MATLAB 的 mex 文件
VC 编译 MATLAB 的 mex 文件mex 文件是 MATLAB 调用其他程序设计语言程序或算法的接口.在 Windows 环境中,mex 文件是扩展文件名为 DLL 的动态链接库,可以在 m ...
- 从C++入手,探寻java的特点
java的特点 java语言建立在成熟的算法语言和坚实的面向对象理论的基础上,具有强大的应用系统设计能力,其具备的跨平台特型,其具备的跨平台特型.面向对象和可靠性.安全性等特点是它能够充分适应网络需要 ...
- linux学习(二)认识Linux
一.Linux系统的组成 linux内核(linus 团队管理) shell:用户与内核交互的接口 文件系统:ext3.ext4等.windows 有 fat32 .ntfs 第三方应用软件 二.Li ...
- Spring Boot学习(三)解析 Spring Boot 项目
一.解析 pom.xml 文件 <?xml version="1.0" encoding="UTF-8"?> <project xmlns=& ...
- Spring学习(一)初识Spring
什么是Spring 定义:Spring 是一个轻量级的 DI / IoC 和 AOP 容器的开源框架,目的为了简化java开发. DI:注入 IOC:控制反转 AOP:面向切面编程 原理:利用了jav ...
- 爬虫日志监控 -- Elastc Stack(ELK)部署
傻瓜式部署,只需替换IP与用户 导读: 现ELK四大组件分别为:Elasticsearch(核心).logstash(处理).filebeat(采集).kibana(可视化) 在elastic官网下载 ...