您的位置:首页 > 编程语言

排列搜索(英雄会在线编程题)解答和搜索可视化(已贴出代码)

2014-02-13 19:21 204 查看
题目出自:http://hero.csdn.net/Question/Details?ID=292&ExamID=287

题目详情

设数组a包含n个元素恰好是0..n - 1的一个排列,给定b[0],b[1],b[2],b[3]问有多少个0..n-1的排列a,满足(a[a[b[0]]]*b[0]+a[a[b[1]]]*b[1]+a[a[b[2]]]*b[2]+a[a[b[3]]]*b[3])%n==k ?

输入包含5个参数:N,K,B0,B1,B2,B3,其中 4<= N<12, 0 <= K,B0,B1,B2,B3 < N。

【思路分析】

最直观的思路就是,每输出一个0~n的排列a[],就验证是否满足条件,满足则ans++。当输出0~n的所有的排列时也就求出了ans。

写过全排列的都知道,n个数的全排列需要计算n!次。一般n>9,用个人电脑计算的话,都会感受到计算时间了。n==12果断会超时。

当然你也可以按这个思路写一次,可以与优化算法后的程序对拍,验证结果。

用回溯法写全排列很简单,可以看我过去写的文章:/article/6991308.html

换个思路,只枚举B0~B3所指的那些a[],考虑到B0~B3还可以重复,所以最多需要给4个位置枚举。

是这样吗?别忘了公式加法因子左边并不是a[b[0]],而是再多一层映射,是a[a[b[0]]]。

假设现在x = {a[b[i]]}( i = [0, 3])已经被枚举出来了,但它们映射后对应四个a[x]却不一定被枚举过,

所以最坏的情况是还需要对4个位置枚举。

综上可见最多枚举8个位置,也就是说程序最坏的时间等同于求0~7的全排列的时间。

//补充:第一次阅读可跳过

最坏时间效率情况,有读者指出应当是相当于求排列A(N, 8)。

我想,最极端坏的情况是N==12时,出现需要枚举的刚好是最后8个位置[4,11]。那么枚举平均次数应该(5+12)/2 = 8.5,也很接近A(8, 8)效率。

还有从整体出发,若第一次映射枚举4个位置(肯定不同),这4个位置映射的新位置也必然不同,从剩下8个数字枚举4个数字作为新位置,碰撞的概率是1/2。

碰撞1次就相当于当前搜索路径的深度减1。

综上,无论如何,最坏效率不会高于A(8.5, 8),平均效率由于我是新手,不会求哈==!,目测挺好的,求大神指点。

最好的情况是b[0~3]都是同一个数,而且有a[b[0]] == a[a[b[0]]],那么只需要枚举1个位置,也就是计算机执行n次。

假如n == 11,总共枚举了5个位置,那么剩下11-5 == 6个位置没有枚举过,

此时5个位置足够计算是否满足条件了,如果满足条件,则ans += 6!

因为剩下6个位置的元素可以随便放,总的可能情况有6!。

【来一个详细例子你就完全明白了】

输入4 0 1 1 1 0

即n == 4, k == 0, b[] = {1, 1, 1, 0}

搜索空间树应该是是这样的(字典序枚举):

root
___________________________|___________________________
|                 |                 |                 |
0                 1                 2                 3
______|______     ______|______     ______|______     ______|______
|     |     |     |     |     |     |     |     |     |     |     |
1     2     3     0     2     3     0     1     3     0     1     2
_|__  _|__        _|__  _|__  _|__  _|__  _|__  _|__  _|__  _|__
|  |  |  |        |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
1  3  1  2        0  3  0  2  1  3  0  3  0  1  1  2  0  2  0  1
|  |              |  |
1  0              1  0


树中节点(除总的树根)就是在各个位置上枚举出来的数字。

初始化a[]使每个元素为-1,值为-1的元素表示这个位置没有枚举过。

(1)先看第一层映射a[b[i]]需要枚举的位置

由于b[i]中有三个重复,也就是说有2个位置b[0]和b[3]需要枚举

按字典序,a[b[0]]枚举为0,a[b[3]]枚举为1,(上图红色数字所示路径)

此时第一层隐射枚举完毕

(2)第二层映射a[a[b[i]]]

因为a[a[b[0]]] == a[0] != -1,也就是枚举过了,不需要再枚举(扩展节点)

同样a[ab[3]]] == a[1] != -1,也不需要再枚举。所以搜索的深度是2

读者可以验证蓝色数字所在路径,是需要枚举4次的。

一楼读者建议AC代码在比赛结束后给出,我觉得有道理,等比赛结束再贴完整代码

//贴出代码如下:2014-02-27增加

#include <stdio.h>
#include <string.h>

int fac[12] = { 1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800 };
int a[12], v[12], n, k, b[4], ans, cnt;

void dfs2(int step)
{
int i;

if (step == 4) {
if ( (a[a[b[0]]]*b[0] + a[a[b[1]]]*b[1] + a[a[b[2]]]*b[2] + a[a[b[3]]]*b[3]) % n == k ) {
ans += fac[n-cnt];
}
return;
}
if (a[a[b[step]]] != -1) {
dfs2(step+1);
return; //!
}
for (i = 0; i < n; ++i) {
if (!v[i]) {
v[i] = 1;
cnt++;
a[ a[b[step]] ] = i;
dfs2(step+1);
a[ a[b[step]] ] = -1;
cnt--;
v[i] = 0;
}
}
}

void dfs1(int step)
{
int i;

if (step == 4) {
dfs2(0);
return;
}
if (a[b[step]] != -1) {
dfs1(step+1);
return;
}
for (i = 0; i < n; ++i) {
if (!v[i]) {
v[i] = 1;
cnt++;
a[ b[step] ] = i;
dfs1(step+1);
a[ b[step] ] = -1;
cnt--;
v[i] = 0;
}
}
}

int howmany (int N,int K,int B0,int B1,int B2,int B3)
{
n = N, k = K, b[0] = B0, b[1] = B1, b[2] = B2 ,b[3] = B3;
memset(a, -1, sizeof a);
ans = cnt = 0;
dfs1(0);
return ans;
}

int main(void)
{
printf("%d\n", howmany(5, 2, 1, 2, 3, 4));
return 0;
}


由于是做题代码不考虑代码复用性和鲁棒性。

其中fac数组保存阶乘,fac[5] == 5!,其中fac[0]应该为1。

v数组标记i是否枚举过,v[i]==1表示数字i已经没枚举过了。

这里DFS1对应第一层映射需要枚举的位置,DFS2对应第二层。

DFS看起来做固定4次枚举(扩展节点),其实对于重复情况直接跳到下一层。但记得回溯时要return

cnt变量记录搜索深度(也就是枚举的位置有几个)。最后满足条件则ans += fac[n-cnt]。

很容易把两层映射的DFS合并,只需设置一个a[]下标x表示当前要枚举的位置。

第一层映射(step < 4),x当然是b[step],第二层x就是a[b[step-4]]了。这样写代码不仅短了,同样也保持了清晰易懂。

【简化后代码如下】

/*
*CopyRight (C) Zhang Haiba
*Date: 2014-02-12
*FileName: csdn10_reduce.c
*
*this prog to solve the problem http://hero.csdn.net/Question/Details?ID=292&ExamID=287 *and reduce code form csdn10.c
*/

#include <stdio.h>
#include <string.h>

int fac[12] = { 1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800 };
int a[12], v[12], n, k, b[4], ans, cnt;

void dfs(int step)
{
int i;
int x = step < 4 ? b[step] : a[b[step-4]];

if (step == 8) {
if ( (a[a[b[0]]]*b[0] + a[a[b[1]]]*b[1] + a[a[b[2]]]*b[2] + a[a[b[3]]]*b[3]) % n == k )
ans += fac[n-cnt];
} else if (a[x] != -1) {
dfs(step+1);
} else {
for (i = 0; i < n; ++i) {
if (!v[i]) {
v[i] = 1;
cnt++;
a[x] = i;
dfs(step+1);
a[x] = -1;
cnt--;
v[i] = 0;
}
}
}
}

int main(void)
{
memset(a, -1, sizeof a);
scanf("%d%d%d%d%d%d", &n, &k, &b[0], &b[1], &b[2], &b[3]);
ans = cnt = 0;
dfs(0);
printf("%d\n", ans);
return 0;
}


【最后,我们通过打印搜索空间树,看看这个算法对于不同输入的表现】

我们修改一下代码就可以借助tree工具来打印,修改代码如下:

/*
*CopyRight (C) Zhang Haiba
*Date: 2014-02-12
*FileName: csdn10_tree_show.c
*
*this prog to solve the problem http://hero.csdn.net/Question/Details?ID=292&ExamID=287 *and using tree tools to print search space tree
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h> //for system()
#define CMD_LEN 128 //for char cmd[]

int fac[12] = { 1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800 };
int a[12], v[12], n, k, b[4], ans, cnt;

void dfs(int step, FILE* fd) //add FILE* fd
{
int i;

int x = step < 4 ? b[step] : a[b[step-4]];
if (step == 8) {
if ( (a[a[b[0]]]*b[0] + a[a[b[1]]]*b[1] + a[a[b[2]]]*b[2] + a[a[b[3]]]*b[3]) % n == k )
ans += fac[n-cnt];
} else if (a[x] != -1)
dfs(step+1, fd);
else {
for (i = 0; i < n; ++i) {
if (!v[i]) {
v[i] = 1;
cnt++;
a[x] = i;
fprintf(fd, "(%d", i); //add
dfs(step+1, fd);
fprintf(fd, ")"); //add
a[x] = -1;
cnt--;
v[i] = 0;
}
}
}
}

void show_by_tree(void)
{
char cmd[CMD_LEN];

sprintf(cmd, "rm -f ./tree_src.txt");
system(cmd);

FILE *fd = fopen("./tree_src.txt", "a+");
fprintf(fd, "\n\t\\tree(root");
dfs(0, fd);
fprintf(fd, ")\n\n");
fclose(fd);

sprintf(cmd, "cat ./tree_src.txt | ~/tree/tree");
system(cmd);
}

int main(void)
{
memset(a, -1, sizeof a);
scanf("%d%d%d%d%d%d", &n, &k, &b[0], &b[1], &b[2], &b[3]);
ans = cnt = 0;
show_by_tree();
printf("%d\n", ans);
return 0;
}


关于tree工具的使用,前面的文章也有介绍,可以看:二叉排序树删除、搜索、插入的递归实现 link(public)

代码测试示范:

ZhangHaiba-MacBook-Pro:code apple$ ./a.out
4 0 3 2 1 0

root
___________________________|___________________________
|                 |                 |                 |
0                 1                 2                 3
______|______     ______|______     ______|______     ______|______
|     |     |     |     |     |     |     |     |     |     |     |
1     2     3     0     2     3     0     1     3     0     1     2
_|__  _|__  _|__  _|__  _|__  _|__  _|__  _|__  _|__  _|__  _|__  _|__
|  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
2  3  1  3  1  2  2  3  0  3  0  2  1  3  0  3  0  1  1  2  0  2  0  1
|  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
3  2  3  1  2  1  3  2  3  0  2  0  3  1  3  0  1  0  2  1  2  0  1  0

4
ZhangHaiba-MacBook-Pro:code apple$ ./a.out
4 0 1 1 0 0

root
___________________________|___________________________
|                 |                 |                 |
0                 1                 2                 3
______|______     ______|______     ______|______     ______|______
|     |     |     |     |     |     |     |     |     |     |     |
1     2     3     0     2     3     0     1     3     0     1     2
_|__  _|__        _|__  _|__  _|__  _|__  _|__  _|__  _|__  _|__
|  |  |  |        |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
1  3  1  2        0  3  0  2  1  3  0  3  0  1  1  2  0  2  0  1
|  |              |  |
1  0              1  0

8
ZhangHaiba-MacBook-Pro:code apple$ ./a.out
4 0 0 0 0 0

root
_____________|______________
|        |        |        |
0        1        2        3
___|___  ___|___  ___|___
|  |  |  |  |  |  |  |  |
0  2  3  0  1  3  0  1  2

24
ZhangHaiba-MacBook-Pro:code apple$ ./a.out
5 1 0 0 0 0

root
________________________|________________________
|           |           |           |           |
0           1           2           3           4
____|_____  ____|_____  ____|_____  ____|_____
|  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
0  2  3  4  0  1  3  4  0  1  2  4  0  1  2  3

0


看以看到——

第1个用例b[i]全部不重复,必定当且仅当枚举4个位置,打印出来的其实就是一个0~3的全排列搜索空间树;

第2个用例实际b[i]有2个不重复,所以枚举位置个数2~4个都有,搜索空间树是参差不齐的;

第3个用例实际b[i]只有1个不重复,搜索空间范围更小了;

第4个用例有5个位置,b[i]却只有1个,实际上枚举空间也非常小,当然这个时候也是最好的情况。

@Author: 张海拔

@Update: 2014-2-27

@Link: http://www.cnblogs.com/zhanghaiba/p/3548602.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: