洛谷1155 NOIP2008 双栈排序【二分图染色】

【正确解法】

这道题大概可以归结为如下题意:

有两个队列和两个栈,分别命名为队列1(q1),队列2(q2),栈1(s1)和栈2(s2)。最初的时候,q2s1s2都为空,而q1中有n个数(n<=1000),为1~n的某个排列。

现在支持如下四种操作:

a操作,将 q1的首元素提取出并加入s1的栈顶。

b操作,将s1的栈顶元素弹出并加入q1的队列尾。

c操作,将 q1的首元素提取出并加入s2的栈顶。

d操作,将s2的栈顶元素弹出并加入q1的队列尾。

请判断,是否可以经过一系列操作之后,使得q2中依次存储着123,…,n。如果可以,求出字典序最小的一个操作序列。

 

这道题的错误做法很多,错误做法却能得满分的也很多,这里就不多说了。直接切入正题,就是即将介绍的这个基于二分图的算法。

 

注意到并没有说基于二分图匹配,因为这个算法和二分图匹配无关。这个算法只是用到了给一个图着色成二分图。

 

第一步需要解决的问题是,判断是否有解。

 

考虑对于任意两个数q1[i]q1[j]来说,它们不能压入同一个栈中的充要条件是什么(注意没有必要使它们同时存在于同一个栈中,只是压入了同一个栈)。实际上,这个条件p:存在一个k,使得i<j<kq1[k]<q1[i]<q1[j]

 

首先证明充分性,即如果满足条件p,那么这两个数一定不能压入同一个栈。这个结论很显然,使用反证法可证。

 

假设这两个数压入了同一个栈,那么在压入q1[k]的时候栈内情况如下:q1[i]q1[j]

 

因为q1[k]q1[i]q1[j]都小,所以很显然,当q1[k]没有被弹出的时候,另外两个数也都不能被弹出(否则q2中的数字顺序就不是123,…,n)

而之后,无论其它的数字在什么时候被弹出,q1[j]总是会在q1[i]之前弹出。而q1[j]>q1[i],这显然是不正确的。

 

接下来证明必要性。也就是,如果两个数不可以压入同一个栈,那么它们一定满足条件p。这里我们来证明它的逆否命题,也就是"如果不满足条件p,那么这两个数一定可以压入同一个栈。"

 

不满足条件p有两种情况:一种是对于任意i<j<kq1[i]<q1[j]q1[k]>q1[i];另一种是对于任意i<jq1[i]>q1[j]

 

第一种情况下,很显然,在q1[k]被压入栈的时候,q1[i]已经被弹出栈。那么,q1[k]不会对q1[j]产生任何影响(这里可能有点乱,因为看起来,当q1[j]<q1[k]的时候,是会有影响的,但实际上,这还需要另一个数r,满足j<k<rq1[r]<q1[j]<q1[k],也就是证明充分性的时候所说的情况…而事实上我们现在并不考虑这个r,所以说q1[k]q1[j]没有影响)

 

第二种情况下,我们可以发现这其实就是一个降序序列,所以所有数字都可以压入同一个栈。

这样,原命题的逆否命题得证,所以原命题得证。

 

此时,条件pq1[i]q1[j]不能压入同一个栈的充要条件也得证。

 

这样,我们对所有的数对(ij)满足1<=i<j<=n,检查是否存在i<j<k满足p1[k]<p1[i]<p1[j]。如果存在,那么在点i和点j之间连一条无向边,表示p1[i]p1[j]不能压入同一个栈。此时想到了什么?那就是二分图~

 

二分图的两部分看作两个栈,因为二分图的同一部分内不会出现任何连边,也就相当于不能压入同一个栈的所有结点都分到了两个栈中。

 

此时我们只考虑检查是否有解,所以只要O(n)检查出这个图是不是二分图,就可以得知是否有解。

 

此时,检查有解的问题已经解决。接下来的问题是,如何找到字典序最小的解。

 

实际上,可以发现,如果把二分图染成12两种颜色,那么结点染色为1对应当前结点被压入s1,为2对应被压入s2。为了字典序尽量小,我们希望让编号小的结点优先压入s1

 

又发现二分图的不同连通分量之间的染色是互不影响的,所以可以每次选取一个未染色的编号最小的结点,将它染色为1并从它开始DFS染色,直到所有结点都被染色为止。这样,我们就得到了每个结点应该压入哪个栈中。接下来要做的,只不过是模拟之后输出序列啦~

 

还有一点小问题,就是如果对于数对(ij),都去枚举检查是否存在k使得p1[k]<p1[i]<p1[j]的话,那么复杂度就升到了O(n^3)。解决方法就是,首先预处理出数组bb[i]表示从p1[i]p1[n]中的最小值。接下来,只需要枚举所有数对(ij),检查b[j+1]是否小于p1[i]p1[i]是否小于p1[j]就可以了。


代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
using namespace std;

int getint()
{
    int i=0,f=1;char c;
    for(c=getchar();(c>'9'||c<'0')&&c!='-';c=getchar());
    if(c=='-')f=-1,c=getchar();
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}

const int N=1005,M=2e6+5;
int n;
int a[N],mi[N],c[N],skt1[N],skt2[N],top1,top2;
int tot,first[N],nxt[M],to[M];

void add(int x,int y)
{
    nxt[++tot]=first[x],first[x]=tot,to[tot]=y;
}

void dfs(int u)
{
    for(int e=first[u];e;e=nxt[e])
    {
        int v=to[e];
        if(!c[v])
        {
            c[v]=3-c[u];
            dfs(v);
        }
        if(c[u]==c[v])
        {
            cout<<0;
            exit(0);
        }
    }
}

int main()
{
    //freopen("lx.in","r",stdin);
    n=getint();
    for(int i=1;i<=n;i++)a[i]=getint();
    mi[n]=a[n];
    for(int i=n-1;i>=1;i--)mi[i]=min(mi[i+1],a[i]);
    for(int i=1;i<n-1;i++)
        for(int j=i+1;j<n;j++)
            if(a[i]<a[j]&&mi[j+1]<min(a[i],a[j]))
                add(i,j),add(j,i);
    for(int i=1;i<=n;i++)
        if(!c[i])
        {
            c[i]=1;
            dfs(i);
        }
    int now=1;
    for(int i=1;i<=n;i++)
    {
        if(c[i]==1)
        {
            skt1[++top1]=a[i];
            cout<<"a ";
        }
        else
        {
            skt2[++top2]=a[i];
            cout<<"c ";
        }
        while(skt1[top1]==now||skt2[top2]==now)
        {
            if(skt1[top1]==now)
            {
                now++;
                top1--;
                cout<<"b ";
            }
            if(skt2[top2]==now)
            {
                now++;
                top2--;
                cout<<"d ";
            }
        }
    }
    return 0;
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值