题目描述
在移动应用开发中,手势锁是一种常见的保护用户数据安全的手段。
现在小明也参与到一个新型手势锁的开发组中,负责开发其中的手势判断模块。这个新型的手势锁是通过用户在3*3的点阵界面上,通过连结点阵中的点,绘制出的图案确定的。用户在设置锁时,先绘制一个图形,在解锁时,只要绘制完全一致的图形,即可解锁成功。一个典形的手势锁如下图。

为方便描述,我们给阵中的点进行如下的编号。

以下是用户绘制手势锁图形的过程:
1. 选择一个点,并用手指触摸屏幕该点;
2. 执行步骤3一次或以上,然后执行步骤4;
3. 选择另一个点,并在手指不离开屏幕的情况下,移动到该点,这样视作手指移动前的点与手指移动后的点之间有一条连线;
4. 手指离开屏幕,绘制图形完成。

绘制手势锁图形的过程中有以下规则:
1. 至少选择两个点;
2. 同一个点,不能选择两次或以上;
3. 连线可以交叉或重叠;
4. 移动手指时,中间可以绕过其它点,例如下图中,可以把手指从(0, 0)直接移动到(0, 2),而无需选择(0, 1)。
判断两个手势图形,有以下规则:
1. 图形不能旋转、平移以及缩放。例如图形(0, 0) -> (0,1)和(1, 0) -> (1, 1)是两种不同的图形,因为图形不能平移;
2. 判断依据为图形中的连线形状,不包括图形中的点。例如图形(0, 0) -> (0,2)以及(0, 0) -> (0, 1) -> (0, 2)的连线形状是一样的,选择的点不一样,依然可以看作同一种图形;
3. 判断依据为图形中的连线形状,不包括连线的方向。例如图形(0, 0) -> (0,2)以及(0, 2) -> (0, 0)的连线形状是一样的,方向是相反的,依然可以看作同一种图形;
4. 重叠的连线并不影响图形的形状。例如图形(0, 0) -> (0,2)以及(0, 0) -> (0, 2) -> (0, 1),虽然其中第二个图形中的(0, 0) -> (0, 2)与(0, 2) -> (0,1)有重叠部分,但重叠的部分不影响图形形状,依然可以看作同一种图形;
在以上规则中,能绘制出和下图相同图形的手势包括但不限于:
(0, 0) -> (0, 2) -> (0,1) -> (2, 1)
(1, 1) -> (2, 1) -> (0, 1) ->(0, 2) -> (0, 0)
(0, 2) -> (0, 0) -> (0, 1) ->(1, 1) -> (2, 1)

现在为了验证该手势锁的安全性,需要计算出总共能绘制出多少种不同的图形。而在多次独立测试中,可能把点阵中的某些点去掉(即不能被选择)。
输入描述:
输入的第一行为一个正整数T,表示测试数据组数。
接下来有T组数据。每组数据有3行。每一行为一个长度为3的字符串,字符串中只包含字符'.'和'X',表示当前要测试的点阵,'.'表示该点可以被选择,'X'表示该点不可以被选择。
数据保证至少有两个点可以选择。
数据范围:
对于所有数据,都满足1<=T<=10。
输出描述:
对于每一组数据,输出一行,包含一个整数,为在给定的点阵中可以绘制的不同的图形数量。
示例1
输入
3
...
XXX
XXX
...
XXX
X.X
.X.
X.X
.X.
输出
3
22
111
分析:
题目中最困难的是如何判断两个手势绘制出来的图像是否相同。我们用字符串来表示绘制的图像,将各个点编号,一条线段两端的序号分别作为十位和个位,将字符串对应下标出设为1,表示存在该线段。
解题思路:
①根据输入获取所有的可用点;
②排列出所有可用点的子序列(大小至少为2);
③画出子序列的图像,并比较是否已经存在相同的图像。
手势图像表达方式:
①将各个点按照如下顺序编号

②每条线段的两个顶点中,序号小的作为十位,大的作为个位,将字符串中对应的字符设为1
比如0,1顶点相连,得到:01000 00000 00000 ...
1,2顶点相连,得到:00000 00000 01000 ...
③从图中可以看出,部分线段会跨过一个中间点,如0和8之间跨过了4,我们做出规定,将这样的线段分成两段,一共有8条这样的线段:
横向 |
0->2 |
0->1->2 |
3->5 |
3->4->5 |
6->8 |
6->7->8 |
纵向 |
0->6 |
0->3->6 |
1->7 |
1->4->7 |
2->8 |
2->5->8 |
斜向 |
0->8 |
0->4->8 |
2->6 |
2->4->6 |
部分代码:
生成手势图像(字符串):
string int2str(int index1, int index2)
{
if (index2 < index1)
swap(index1, index2);
string tmpstr(100, '0');
// xiexian
if (index1 == 0 && index2 == 8)
{
tmpstr[4] = '1';
tmpstr[48] = '1';
return tmpstr;
}
if (index1 == 2 && index2 == 6)
{
tmpstr[24] = '1';
tmpstr[46] = '1';
return tmpstr;
}
// hengxian
if (index1 == 0 && index2 == 2)
{
tmpstr[1] = '1';
tmpstr[12] = '1';
return tmpstr;
}
if (index1 == 3 && index2 == 5)
{
tmpstr[34] = '1';
tmpstr[45] = '1';
return tmpstr;
}
if (index1 == 6 && index2 == 8)
{
tmpstr[67] = '1';
tmpstr[78] = '1';
return tmpstr;
}
// shuxian
if (index1 == 0 && index2 == 6)
{
tmpstr[3] = '1';
tmpstr[36] = '1';
return tmpstr;
}
if (index1 == 1 && index2 == 7)
{
tmpstr[14] = '1';
tmpstr[47] = '1';
return tmpstr;
}
if (index1 == 2 && index2 == 8)
{
tmpstr[25] = '1';
tmpstr[58] = '1';
return tmpstr;
}
tmpstr[index1 * 10 + index2] = '1';
return tmpstr;
}
合并手势图像(字符串合并):
string combine(string & str, string tmp)
{
string res;
for (int i = 0; i < 100; i++)
{
if (tmp[i] == '1' || str[i] == '1')
res.push_back('1');
else
res.push_back('0');
}
return res;
}
完整代码:
#include
#include
#include
完整代码修改版(子串全排列)
代码中还有一些可以优化的地方:
在寻找所有的连接顶点的顺序的时候(即所有子串的全排列),使用了stl的next_permutation函数,导致了多层重复求解所有顶点的全排列。
修改了第140到182行。
#include
#include
#include
测试样例:
前三个是官方给的样例
输入:
5
...
000
000
...
000
0.0
.0.
0.0
.0.
.0.
000
.0.
...
.0.
...
输出:
3
22
111
30
44934