双栈排序(洛谷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至少有两个顶点,且其所有回路的长度均为偶 ...
随机推荐
- jmeter压测以及用Badboy录脚本
一.压测时的基本配置: 1.设置线程数.延迟几秒.循环次数打勾表示一直去循环.调度器打勾可以填写持续时间.启动时间等 线程数:就是并发的用户数 N Ramp-Up Period(in second ...
- css常用属性之绝对定位、相对定位、滚动条属性、背景图属性、字体、鼠标、超链接跳转页面
1.绝对定位position: fixed(比如广告页面向下滑动的时候,页面最上方有个标题不能随之滑动,就需要用到position: fixed,同时还需要用到一个标签(标签高度很高才会出现滚动的情况 ...
- python变量及简单数据类型
python 目录 python 1.变量 1.变量的定义 2.变量的命名 3. 关键字 4.变量的命名规则 5.变量的类型 5.不同类型变量之间的计算 6.变量的输入 7.变量的格式化输出 8.格式 ...
- JDK安装与基础环境变量配置 入门详解 - 精简归纳
JDK安装与基础环境变量配置 JERRY_Z. ~ 2020 / 9 / 17 转载请注明出处!️ 目录 JDK安装与基础环境变量配置 一.下载 二.安装 (1).双击.exe文件 (2).全选安装工 ...
- 程序员必须了解的知识点——你搞懂mysql索引机制了吗?
一.索引是什么 MySQL官方对索引的定义为:索引(Index)是帮助MySQL 高效 获取数据的数据结构,而MYSQL使用的数据结构是:B+树 在这里推荐大家看一本书,<深入理解计算机系统的书 ...
- Erlang+RabbitMQ Server的详细安装
Erlang(['ə:læŋ])是一种通用的面向并发的编程语言, 它有瑞典电信设备制造商爱立信所辖的CS-Lab开发,目的是创造一种可以应对大规模并发活动的编程语言和运行环境. Erlang官网:ht ...
- 容器云平台No.3~kubernetes使用
今天是是第三篇,接着上一篇继续 首先,通过kubectl可以看到,三个节点都正常运行 [root@k8s-master001 ~]# kubectl get no NAME STATUS ROLES ...
- Unity 自己使用顶点描绘圆形UI图片
2020-09-10 在游戏的UI中,圆形图片的需求是很高的,但是,在Unity中想要实现圆形UI,一般的做法是是使用圆形Mask(遮罩),但是使用Mask的缺点很明显,主要有三点: 1.比较麻烦,使 ...
- JVM学习(八)指令重排序
一.数据依赖性 在学习JVM的指令重排序之前,我们先了解一下什么是数据依赖性: 编译器和处理器在处理具体的指令时,可能会对操作进行重排序来提高执行性能[多条指令并行执行,所以提升性能的同时也可能会导致 ...
- Python推导式(列表推导式、元组推导式、字典推导式和集合推导式)
列表表达式 a_range = range(10) # 对a_range执行for表达式 a_list = [x * x for x in a_range] # a_list集合包含10个元素 pri ...