POJ-1780-Code

本文深入解析POJ-1780题目的算法解决方案,通过构建欧拉回路解决密码解锁问题,阐述如何找到字典序最小的解锁序列,避免使用递归,采用数组模拟栈进行高效计算。

POJ-1780-Code

传送门

这道题是欧拉回路。emmm。从昨天写到今天

题目大意:破解一个n位的密码,最多输入10^n + n - 1位可以解锁。要你按字典最小序输出这10 ^ n + n - 1位。

先来说说为什么最多需要10^n + n - 1位?
n位密码共有10^n种不同的排列。
另外根据题目中的描述:如果正确的密码是4567,当你按下456时,当前状态达到456,然而你后面一位按的是8,那么当前状态将会转到568。
这句话,看似没啥,但实际上给了我们解题思路:
要求包含10^n组数据的n位数最短,那么这10 ^ n组数出现且仅出现一次,并且前一组的后n-1位是后一组的前n-1位。
(比如密码若为4位,12345,这5个数字包含了2组数,分别是1234和2345)
那么这样就可以使得我们按的密码位数最少了。
一共有10^n种方案,我们选取第一种方案的n位数,按照上面说的,后面每添加一位就会增加一种方案,所以要构成10 ^ n种方案,我们后面就需要加上10 ^ n - 1位,构成10 ^ n种方案(这里第一种方案包括在其中,所以只需要增加10 ^ n - 1位)
所以我们的最少总位数是:10^n + n - 1位。

那么这里就可以得到我们的解题思路:
我们把n-1位数看成一个顶点,顶点与顶点相连接的是一个一位数,这样顶点和边相连就构成了一个n位数(也就是密码方法之一)
这样我们的一个顶点最多可以连10条边。(一个一位数:0~9)
图示:
1
把这些顶点单独的构造:
这样我们就有10^(n-1)个顶点(因为一个n-1位的数字有10 ^ (n-1)种构造方案)
有10^n条边,因为一个顶点有10条边,那么就是10 ^ (n-1) * 10条边。

我当初想这道题和欧拉回路之间的过程卡了很久。。

实质上是这样的:我们把这个图构造出来了,现在构造出来的图形一共有10^n次方种不同的n位数,我们要把这些数字全都走过一遍。构成我们输出的ans,那么我们按照上面的策略前一组的后n-1位是后一组的前n-1位。
比如我们上面的图:如果走3340(0这条边的话)我们下一个要找的顶点就是340的这个顶点。然后选一条边走即可,重复上述过程…
(懂了吗,反正我自己都懂了哈哈哈)

这样理解下来就是要把所有的路都走一遍,这就是我们的欧拉回路了~就联系起来啦: )

然后我们这个题目不能用递归,n最大等于6,递归深度可以达到10^6层,很明显会爆栈。
所以用数组模拟栈来操作~

node[]数组存储的是当前下标的顶点有多少条边。
sta[]数组存储的就是我们当前走的路了。
ans[]存储我们的答案。
sta[]是从小到大存储的
(我们在写欧拉回路的题时使用dfs打印路径的时候都是在dfs后面开始打印的,这里的原因不作过多解释,在后面开始打印才可以保证全部的路都走到)
所以我们在存储ans[]的时候也是从sta[]的后面开始存储,
要求字典序最小。前面说的sta[]是从小到大的,ans[]又是从sta[]后面开始存的(这样存下来的是从大到小的),所以我们输出的时候从后往前输出就可以得到我们的最小字典序了~

代码部分的solve()函数就是模拟dfs过程(我们让当前操作顶点所连的边数到达10就可以退出了)

void solve(int v)
{
	int w;
	while (node[v] < 10)
	{
		w = 10 * v + node[v];
		node[v]++;
		sta[sum++] = w;
		v = w % m;
	}
}

m是(10^(n-1)次方),模m的操作,就是我们要让顶点的位数小于n位
(比如n=4,那么10^(4-1)次方是1000,模1000就是要让当前位数小于4位)
因为node[v]是从小到大(0~9)所以我们走的时候就按照node[v]这个数来(是一个小tip)
然后w = 10 * v + node[v]这个操作就是选一条边的过程,得到n位数。入栈。
v = w % m;就是选取前一组的后n-1位;也就是上面说的要找的下一个顶点~

然后就是main函数部分就是ans[]的存储,从后往前开始存(这里相当于dfs的输出部分),然后用走到这条路的那个顶点再进入solve()继续走,(这里相当于dfs输出后回到上一层递归的地方继续操作)
然后判断条件就是栈空的时候就可以退出啦~

啊哈~代码部分解释完啦
(我真的搞了超级久,,是我太蠢了吗。。。生无可恋!!)
划重点!!

代码部分:

#include <iostream>
#include <cstdio>
using namespace std;
const int N = 1e6 + 10;

char ans[N];
int node[N];
int sta[N];
int n, m;
int cnt;
int sum;

void solve(int v)
{
	int w;
	while (node[v] < 10)
	{
		w = 10 * v + node[v];
		node[v]++;
		sta[sum++] = w;
		v = w % m;
	}
}

int main()
{
	while (~scanf ("%d", &n) && n)
	{
		int w = 0;
		m = 1;
		cnt = 0;
		sum = 0;
		if (n == 1)
		{
			cout << "0123456789\n";
			continue;
		}
		for (int i = 0; i < n - 1; i++)
		{
			m *= 10;
		}
		for (int i = 0; i < m; i++)
		{
			node[i] = 0;
		}
		solve(0);
		while (sum)
		{
			w = sta[--sum];
			ans[cnt++] = w % 10 + '0';
			solve(w / 10);
		}
		for (int i = 1; i < n; i++)
		{
			cout << "0";
		}
		while (cnt)
		{
			cout << ans[--cnt];
		}
		cout << endl;
	}
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

娃娃酱斯密酱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值