NOIP2008 双栈排序

博客详细介绍了NOIP2008竞赛中的一种双栈排序问题,提出了判断数对(i,j)是否满足特定条件P的方法,该方法基于比较S[i]和S[j]以及F[j+1]的关系,具有O(n^2)的时间复杂度。" 104317999,7770417,解决DSO_ROS编译错误:DSO_LIBRARY NOTFOUND问题,"['ROS', 'DSO', '编译', 'CMake', '库文件']
摘要由CSDN通过智能技术生成

分析条件,我们把问题抽象为数学模型。设输入序列为S,考虑S[i],S[j]两个元素不能进入同一个栈的条件.注意,这里所说的"S[i],S[j]两个元素不能进入同一个栈",不是说仅仅不能同时在一个栈中,而是自始至终不能进入一个栈,即如果有解,那么S[i],S[j]一定进入过的栈不同.


结论P: S[i],S[j]两个元素不能进入同一个栈 <=> 存在k,满足i<j<k,使得S[k]<S[i]<S[j]. 证明略过,请参考sqybi.尝试后可以发现结论P是正确的.


把每个元素按照输入序列中的顺序编号,看作一个图中的每个顶点.这时,我们对所有的(i,j)满足i<j,判断是否满足结论P,即S[i],S[j]两个元素能否进入同一个栈.如果满足P,则在i,j之间连接一条边.


我们对图染色,由于只有两个栈,我们得到的图必须是二分图才能满足条件.由于要求字典序最小,即尽量要进入栈1,我们按编号递增的顺序从每个未染色的顶点开始染色,相邻的顶点染上不同的色,如果发生冲突,则是无解的.否则我们可以得到每个顶点颜色,即应该进入的栈.


接下来就是输出序列了,知道了每个元素的决策,直接模拟了.


在判断数对(i,j)是否满足P时,枚举检查是否存在k的时间复杂度是O(n),则总的时间复杂度是O(n^3),对于n=1000是太大了.这原因在于过多得枚举了k,我们可以用动态规划把枚举k变为O(1)的算法.


设F[i]为Min{S[i],S[i+1],S[i+2]..S[n-1],S[n]},状态转移方程为F[i]=Min{ S[i] , F[i+1] }.边界为F[N+1]=极大的值.


判断数对(i,j)是否满足P,只需判断(S[i]<S[j] 并且 F[j+1]<S[i])即可.时间复杂度为O(n^2).

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <stack>
using namespace std;
const int maxn = 1000+5;
bool Edge[maxn][maxn];
int s[maxn],F[maxn], color[maxn];
stack<int> staA,staB;
int n;
void  NoAnswer()
{
      printf("0\n"); 
      exit(0);
}
void  dfs(int x, int c)
{
      color[x] = c;
      for (int i = 1; i <= n; ++i)
      if (Edge[x][i])
      {
          if (color[i] == c ) NoAnswer();
          if (!color[i])
             dfs(i, 3-c);
      }
}
int main()
{
    cin>>n;
    for ( int  i = 1 ;  i <= n ; ++i) cin>>s[i];
    F[n+1] = 0x7fffffff;
    for (int i = n; i>=1; --i) F[i] = min(s[i], F[i+1]);
    
    
    for (int i = 1; i < n  ; ++i)
        for (int j = i+1; j <=n ;++j)
        if (s[i] < s[j] && F[j+1]<s[i])
           Edge[i][j] = Edge[j][i] = true;
           
    for (int i = 1; i <= n;++i)
        if ( !color[i]) dfs(i,1);
        
        
    int aim = 1;
    for (int i = 1; i <= n; ++i)
    {
        if (color[i]==1)
        {
           staA.push(s[i]);
           printf("a ");
        } else
        {
           staB.push(s[i]);
           printf("c ");
        }
        
        while (!staA.empty() && staA.top() == aim || 
               !staB.empty() && staB.top()== aim)
        {
              if (!staA.empty() && staA.top()== aim)
              {
                  staA.pop();
                  printf("b ");
              } else
              {
                  staB.pop();
                  printf("d ");
              }
              aim ++;
        }
    }
    return 0;
}





评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值