`
阿尔萨斯
  • 浏览: 4146805 次
社区版块
存档分类
最新评论

POJ 1780 Code(欧拉回路+模拟栈)

 
阅读更多

POJ 1780 Code(欧拉回路+模拟栈)

http://poj.org/problem?id=1780

题意:(题意较复杂)大致思想是给你一个n,表示一个由0-9数字构成的n位密码.这个解锁过程如下:锁一直读入所有内容.只要当它最后读入的n个数字与密码吻合,那么就表示解锁了.现在要你输出一个长度为10^n+n-1的数字序列,这个数字序列包括了n位密码的所有可能.(也就是说输入这个序列,必定解锁)

分析:

首先题目中说了对于n位密码,锁中存了一个n-1位的当前状态.

可以证明:所有10^(n-1)个状态(一共这么多个,可以自己算算)中,任意一个状态都有一条出边(由0-9构成)和一条入边(由0-9构成),且出边数==入边数(这个可以自己假定n=1,n=2验证一下,注意这里可能存在0000这种自环). 所有这个锁的状态图是一个欧拉图,可以找到一个欧拉回路,正好可以将该欧拉图的所有边走仅一次.(走过所有边一次等同于遍历了所有可能的n位密码一次,想想是不是)

所以对于这道题,我们只需要输出其状态图的欧拉回路即可.不过既然要输出欧拉回路,就要遍历所有的可行边.但是这个图中的边是无数的重复0-9的数字,我们如何标记每一条边呢?

假设n=4,我们用4321(四千三百二十一)表示一条从432状态经过数字1出去的边.这种表示方式我们可以保证可以不重复的标记该状态图中的所有边.

注意(假设n=4)0000一定是一个可能的密码,所以我们把最终结果速度ans的0-3位设为0,且标记vis[0]为true(表示从状态000经0边出去的,这是一个自环.从000经过0边依然是到000状态.注意:我们标记的是每一条边,不是每个状态节点).

剩下的就是模拟栈来打印欧拉路径的事了.这有点麻烦.注意这里我们模拟的过程如下:首先初始状态是状态0,下面

从状态0走00到状态0,(00)

从状态0走01到状态1,(001)

从状态1走10到状态0,(0010)

从状态0走02到状态2,(00102)

从状态2走00到状态0,(001020)

从状态0走03到状态3,(0010203)

当前我们的ans数组已经为(0010203040506070809) 了,我们当前状态是9,且90这条边我们没走过,所以我们将走90边:

从状态9走90到状态0,( 00102030405060708090)

此时我们发现我们现在在状态0,但是0节点的所有出边我们已经走过了一遍(也就是说我们进到0状态就出不去了),这样我们输出不了欧拉回路了,说明我在状态9的时候不应该先走边90,而应该去走边91,92…等,然后我们通过某条边X9回到了状态9之后,最后走边90结束欧拉回路的过程.(因为起点是状态0,终结点也是状态0)

所以对于我们已经从9走了边90到了死胡同0状态,我们应该回退该过程,即标记90边未走过,且把当前状态重新设为9,且我们选择边的起点应该从91(即0的下一个位置)开始选.

上面这个过程是用数组栈模拟深度优先遍历的过程,而不是模拟euler函数的过程.(但是我们如何证明深度优先遍历就一定能输出欧拉回路呢?这个问题我还不知道,其实就是euler递归函数如何非递归实现的问题)

AC代码:

#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=1000000+10;
char ans[maxn];//保存最终的结果
bool vis[maxn];//标记边是否被走过
int main()
{
    int n;
    while(scanf("%d",&n)==1&&n)
    {
        if(n==1)
        {
            printf("0123456789\n");
            continue;
        }

        int mod=1,end=1;
        for(int i=1;i<=n;i++)
        {
            mod*=10;
            end*=10;
        }
        mod /=10;
        end+=n-1;
        for(int i=0;i<n;i++)
            ans[i]='0';
        memset(vis,0,sizeof(vis));
        vis[0]=true;
        int now=0;//now表示的是当前所处的节点状态(即由n-1位数位构成的整数)
        int i=0,pos=n;
        while(pos<end)
        {
            now %=mod;
            for(;i<10;i++)if(!vis[now*10+i])//当前状态节点还有没有走过的边
            {
                vis[now*10+i]=true;
                ans[pos++]=i+'0';
                now = now*10+i;
                i=0;
                break;
            }
            if(i==10&&pos<end)//当前递归层走不通了(即我们进入了一个不存在没走过边的节点),且还没有走完所有的边
            {
                pos--;
                now = (ans[pos-n+1]-'0')*mod + now;
                vis[now]=false;//消除死胡同的标记,因为如果直接在当前状态走边now的话,会进死胡同,所以回退
                i=now%10+1;
                now/=10;
            }
        }
        ans[end]='\0';
        printf("%s\n",ans); //注意:如果此处ans是int输出,一个个的输出每个数字的话,时间从79ms->489ms
    }
    return 0;
}


分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics