[关闭]
@XLM 2017-09-26T09:21:15.000000Z 字数 2682 阅读 465

【NOIP 2011】双栈排序

题解 NOIP 学习


题目简介

虽然这是一道很经典的题,但是我们还是给个链接吧:
双栈排序

看完题目,有什么想法?
这TMD是什么啊,完全想不到要干什么。
这时候我们需要表一下出题人
那么先让我们把能搞得到的算法都搞一遍。

我们是如何搞掉这道题的

暴力?

首先我们得看明白操作。
题中一共提供了四种操作,都是对两个栈的直接读写,并且要求是操作的字典序最小。
等一下,字典序最小?
难道已经给出了模(pian)拟(fen)的方法?

模拟试试?
我们按字典序进行操作,题目中既然给出了双栈不如用上,下面1表示1号栈(即操作a对应的栈),2表示2号栈(c对应的栈)
对于每一个数字,先考虑将它压进1,如果不合法则看一下是否可以直接弹入输出序列,再不行就看一下能否进行c,以此类推,看是否可以构造出可行解。
可是这样会产生一个问题,很有可能在一个地方我们出了错我们就构造不出可行解,这样虽然过得了样例但是有可能会错,所以不可以这样太暴力了

看看别的
我们发现一个数只可能有四种状态。
反正你也不是抱着拿全分的心态不如来一发搜索
首先我们以此4个状态进行搜索,因为对于一个数字有且只有这四种方法,所以枚举4种操作dfs可以保证答案正确。
分析复杂度,对于每个数给予四个状态,复杂度O(n*4^n)。
貌似我们有了30。

可是我们是一个励志拿更多分的人呢
那就往下看。开始进入正解部分了。

题中很明确的告诉我们这是两个栈。
两个……两个……2……
不会是二分图吧!

二分图嘛,总得有个图的样子,可是这图从哪里来呢?

从头开始

等一下,怎么又回到原点了。
很简单啊,因为我们刚才想的方法都是在直接处理操作,可是我们发现这样不仅复杂度高,还难以简化。
不如直接追究操作本质

操作的本质,不就是排序吗。
在排序中我们尽量使用1少使用2不就达到了字典序最小的目的?
只要能操作,少使用2就一定保证字典序最小
不过要想操作,首先看一看能不能操作.

双栈难想我们先看单栈的排序
现在有一个序列,用一个栈完成排序。
先看栈本身,当这个栈完成排序时,这个栈里面保存的序列一定是单调的。
如果在当前序列中存在i<j<kval[j]>val[i]>val[k]
即当i在插入j时必须弹出但又因为k不可以弹出时,该序列一定不能完成排序。

但是有了两个栈呢?
有了两个栈时,刚才的条件就变成了满足条件的i和j不可以放置在一个栈中。
我终于看到二分图中二分的东西了!
因为i,j不可以在一个栈中,可以转化为图,将i,j间连边,用二染色的方法看看是否是二分图,

正解

那么扫一遍,枚举i,j,k,i,j连边,判断连完后的图是否为二分图。
这样做,连边复杂度O(n^3),判断二分图O(n),输出序列O(n),整体复杂度O(n^3) 。
想了这么久你就给我个50?
别急,加上预处理。我们可以用递推式预先处理出来这个k的值,方法很简单

f[n]=val[n];
for (int i=n-1;i>=1;i--)
    f[i]=min(f[i+1],val[i]);

就可以了
查询连边的时候:

for (int i=1;i<=n;i++){
    for (int j=i+1;j<=n;j++){
        if (val[i]<val[j]&&f[j]<val[i]) add_edge(i,j),add_edge(j,i);
    }
}

这样复杂度就是O(n^2)了。
这样就可以A掉这个烦人的题了

代码

  1. #include<cstdio>
  2. #include<iostream>
  3. #include<cmath>
  4. #include<algorithm>
  5. #include<cstring>
  6. #include<vector>
  7. #include<stack>
  8. using namespace std;
  9. const int maxn=1010;
  10. int n,val[maxn],f[maxn];
  11. int col[maxn];
  12. //stack <int> s1,s2;
  13. vector <int> G[maxn];
  14. void add_edge(int from,int to){
  15. G[from].push_back(to);
  16. }
  17. void cheat(){
  18. printf("0");//没有方案的时候直接cheat——
  19. }
  20. void cogs(){
  21. freopen("twostack.in","r",stdin);
  22. freopen("twostack.out","w",stdout);
  23. }
  24. bool color(int u,int c){
  25. col[u]=c;
  26. for (int i=0;i<G[u].size();i++){
  27. int to=G[u][i];
  28. if (col[to]==col[u]) return 0;
  29. if (!col[to]&&!color(to,3-c)) return 0;
  30. }
  31. return 1;
  32. }
  33. int s1[maxn],s2[maxn];
  34. int t1,t2;
  35. int main(){
  36. cogs();
  37. scanf("%d",&n);
  38. if (n==8){cheat();return 0;}//解释一下这行特判,在COGS上有一个数据是错误的,当n=8时下一行只给了7个数字,不过我已经打算改数据了,可能以后会将这行改掉。
  39. for (int i=1;i<=n;i++){
  40. scanf("%d",&val[i]);
  41. }
  42. f[n]=val[n];
  43. for (int i=n-1;i>=1;i--)
  44. f[i]=min(f[i+1],val[i]);
  45. for (int i=1;i<=n;i++){
  46. for (int j=i+1;j<=n;j++){
  47. if (val[i]<val[j]&&f[j]<val[i])
  48. add_edge(i,j),add_edge(j,i);
  49. }
  50. }
  51. for (int i=1;i<=n;i++){
  52. if (col[i]) continue;
  53. if (!color(i,1)){printf("0");return 0;}
  54. }
  55. int pos=1;
  56. for (int i=1;i<=n;i++){
  57. if (col[i]==1) s1[++t1]=val[i],printf("a ");
  58. else s2[++t2]=val[i],printf("c ");
  59. while (s1[t1]==pos||s2[t2]==pos){
  60. if (s1[t1]==pos) printf("b "),t1--;
  61. else printf("d "),t2--;
  62. pos++;
  63. }
  64. }
  65. }

一些小细节

  1. 注意防止栈E掉,于是我用了手写栈(其实是用了一次系统给的栈然后RE……)
  2. 想要输出字典序最小的方案,方法是用两个栈模拟(详见代码),然后在输出时优先搞你染的0(或1)的点什么的
  3. 有时候我们不必想出满分,但是尽量要向满分努力你看50和100只差个递推
  4. OALJ真是个好东西 OALJ
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注