puzzle(4.1)日历拼图

目录

一,规则

二,每日拼图

2022年2月

2022年3月

竖条下滑问题

2022年4月

三,术语

四,启发式搜索策略

1,数独

2,策略一

3,策略二

4,策略三

5,策略四

6,策略五

7,策略六

五,数字化

1,读取图片并二值化

2,边缘检测

3,轮廓检测

4,求解格子尺寸、坐标

5,计算有效轮廓数量

6,坐标微调

7,手动删减轮廓

8,解析空出来的3个格子

8,连通性计算

9,完整代码

六,以解生解

1,大拇指

2,可视化

3,U型

4,新解

5,日期汇总

6,完整代码

七,全文说明


一,规则

每天根据月、日、星期去掉3个格子,剩下的格子刚好全部覆盖。

日历拼图有两千多组合,如果所有组合都能拼的话,那真的太神奇了。

二,每日拼图

2022年2月

根据下面的数字化的方法,2022年2月1日-2月28日的答案分别是:

2月1日周二
  1  0  1  8  2  2  0
  1  1  1  8  8  2  0
  0  3  3  9  8  2  2
 10  3  3  9  9  9  5
 10  4  3  5  5  5  5
 10  4  4  4  6  6  6
 10  4  7  7  7  0  6
  0  0  0  0  7  7  6
2月2日周三
  1  0  1  2  3  3  0
  1  1  1  2  2  3  0
  4  0  9  8  2  3  3
  4  9  9  8  2  5  5
  4  9  6  8 10  5  5
  4  4  6  8 10  7  5
  6  6  6 10 10  7  0
  0  0  0  0  7  7  7
2月3日周四
  1  0  1  2  3  3  0
  1  1  1  2  2  3  0
  4  4  0  2  2  3  3
  8  4  4  4  5  5  5
  8  6  9  9  9  5  7
  8  6  9 10 10  5  7
  8  6  6  6 10 10  7
  0  0  0  0  0  7  7
2月4日周五
 10 0 10  7  1  1  0
 10  10 10  7  7  1  0
  2  2  2  0 7  1  1
  2  4  8  8  8  3  3
  2  4  8  3  3  3  9
  4  4  4  6  5  5  9
  6  6  6  6  5  5  9
  0  0  0  0  5  0  9
2月5日周六
  1  0  2  2  3  3  0
  1  1  2  4  4  3  0
  8  1  2  4  0  3  3
  8  1  2  4  4 10  5
  8  6  9 10 10 10  5
  8  6  9  9  5  5  5
  6  6  6  9  7  7  7
  0  0  0  0  7  7  0
2月6日周日
  1  0  1  3  3  8  0
  1  1  1  3  8  8  0
  2  2  3  3  8  0  5
  9  2  4  4  5  5  5
  9  2 10  4  4  4  5
  9  2 10  6  6  6  7
  9 10 10  0  6  6  7
  0  0  0  0  7  7  7
2月7日周一
  1  0  1  2  2  2  0
  1  1  1  2  3  3  0
  8  8  8  2  4  3  0
  8 10 10  4  4  3  9
 10 10  5  4  4  3  9
  5  5  5  6  6  7  9
  5  6  6  6  0  7  9
  0  0  0  0  7  7  7
2月8日周二
  1  0  1  2  3  3  0
  1  1  1  2  2  3  0
  4  4  4  2  2  3  3
  0  4  8  5  5  5  5
  6  4  8  8  8  7  5
  6  9  9  9  9  7  7
  6  6  6 10 10  0  7
  0  0  0  0 10 10  7
2月9日周三
  1  0  1  2  3  3  0
  1  1  1  2  2  3  0
  4  4  4  8  2  3  3
  4  0  6  8  2  5  5
  4  9  6  8 10  5  5
  9  9  6  8 10  7  5
  9  6  6 10 10  7  0
  0  0  0  0  7  7  7
2月10日周四
  1  0  1  2  3  3  0
  1  1  1  2  2  3  0
  4  4  4  2  2  3  3
  5  4  0  6  6  6  8
  5  4  6  6  8  8  8
  5  5  5  7  7  7  7
  9  9  9  9 10 10  7
  0  0  0  0  0 10 10
2月11日周五
  1  0  1  8  2  2  0
  1  1  1  8  8  2  0
  9  3  3  3  8  2  2
  9  7  3  0  4  4  4
  9  7  3  6  6  5  4
  9  7  6  6  6  5  4
  7  7 10 10 10  5  5
  0  0  0  0 10  0  5

2月12日周六
  1  0  1  2  3  3  0
  1  1  1  2  2  3  0
  4  4  4  2  2  3  3
  8  4  5  5  0  6  6
  8  4  9  5  5  5  6
  8  9  9  7  7  7  6
  8  9 10 10 10  7  6
  0  0  0  0 10  7  0
2月13日周日
  1  0  1  2  3  3  0
  1  1  1  2  2  3  0
  4  4  4  4  2  3  3
  8  9  9  4  2  0  5
  8 10  9  9  5  5  5
  8 10  6  6  6  7  5
  8 10 10  0  6  7  7
  0  0  0  0  6  7  7
2月14日周一
  1  0  1  4  2  2  0
  1  1  1  4  7  2  0
  3  4  4  4  7  2  2
  3  3  3  3  7  5  0
  8  6  6  6  7  5  5
  8  6  6 10 10  9  5
  8  8 10 10 0 9  5
  0  0  0  0  9  9  9
2月15日周二
  7  0  1  8  8  8  0
  7  1  1  2  9  8  0
  7  1  1  2  9  9  3
  7  4  2  2  2  9  3
  0  4 10 10  3  3  3
  4  4 10 5  5  5  5
  4 10 10 5  6  0  6
  0  0  0  0  6  6  6
2月16日周三
  1  0  1  5  2  3  0
  1  1  1  5  2  3  0
  4  5  5  5  2  3  3
  4  4  4  6  2  7  3
  4  0  9  6  6  7  7
  9  9  9  6  6 10  7
  9  8  8  8  8 10 0
  0  0  0  0  8 10 10

2月17日周四
  1  0  2  2  2  3  0
  1  1  6  6  2  3  0
  4  1  6  5  2  3  3
  4  6  6  5  5  5  7
  4  4  0  5 10  8  7
  9  4  9 10 10  8  7
  9  9  9 10 10  8  7
  0  0  0  0  0  8  8
2月18日周五
  1  0  2  2  2  3  0
  1  2  2  3  3  3  0
  1  1  1  4  4  4  4
  5  5  5  5  8  8  6
  5  7  9  0  8  6  6
  7  7  9  8  8  6  6
  7  9  9  9 10 10 10
  0  0  0  0 10  0 10
2月19日周六
  1  0  1  10  10  10  0
  1  1  1  2  2  10  0
  3  4  4  2  2  10  6
  3  5  4  2  6  6  6
  3  5  4  4  0  7  6
  3  5  5  8  7  7  9
  8  8  8  8  7  9  9
  0  0  0  0  7  9  0
2月20日周日
  1  0  1  2  2  2  0
  1  1  1  3  2  7  0
  3  3  3  3  2  7  7
  4  5  5  5  6  6  7
  4  5  8  6  6  0  7
  4  5  8  10  10  9  9
  4  8  8  0  10  9  9
  0  0  0  0  10  10  9
2月21日周一
  1  0  1  2  2  2  0
  1  1  1  4  2  3  0
  4  4  4  4  2  3  3
  5  6  6  6  7  7  3
  5  6  8  8  8  7  0
  5  6  8  9  9  7  7
  5  9  9  9  0 10 10
  0  0  0  0 10 10 10
2月22日周二
  1  0  2  2  3  3  0
  1  1  1  2  4  3  0
  1  5  5  2  4  3  3
  5  5  6  6  4  4  4
  9  9  6  6  6  8  7
  0  9  8  8  8  8  7
  9  9 10 10 10  0  7
  0  0  0  0 10 10  7
2月23日周三
  1  0  1  2  3  3  0
  1  1  1  2  2  3  0
  4  4  4  4  2  3  3
  5  5  5  5  2  6  6
  7  7  7  5  9  6  6
  7  0  8  8  9 10  6
  7  8  8  9  9 10  0
  0  0  0  0 10 10 10
2月24日周四
  1  0  1  2  2  2  0
  1  1  1  5  3  2  0
  4  5  5  5  3  3  7
  4  5  6  6  6  3  7
  4  9  6  6  7  7  7
  4  9  0  8  8  8  8
  9  9  9 10 10 10  8
  0  0  0  0  0 10 10
2月25日周五
  2  0  1  1  1  1  0
  2  2  2  3  3  3  0
  2  5  5  3  4  4  4
  5  5  6  6  6  6  4
  7  8  8  9  9  6  4
  7  7  8 0  9  9  9
  7  7  8  8 10 10 10
  0  0  0  0 10  0 10
2月26日周六
  1  0  1  2  2  2  0
  1  1  1  3  2  4  0
  5  6  6  3  2  4  4
  5  7  6  3  3  3  4
  5  7  6  6  8  8  4
  5  7  7  9  0  8  8
  9  9  9  9 10 10 10
  0  0  0  0 10 10  0
2月27日周日
  1  0  1  5  5  2  0
  1  1  1  5  2  2  0
  3  4  5  5  2  2  7
  3  4  4  6  7  7  7
  3  9  4  6  6  6  6
  3  9  4  8  8  0 10
  9  9  9  0  8  8 10
  0  0  0  0 10 10 10
2月28日周一
  1  0  1  2  2  2  0
  1  1  1  5  2  3  0
  4  5  5  5  2  3  3
  4  5  6  6  7  7  3
  4  4  4  6  6  7  3
  8  9  9  9  9  7  0
  8  8  8  8  0 10 10
  0  0  0  0 10 10 10

拼的时候经常会出现,还有一块拼不上,而空出来的格子组成的是另一个块的形状的情况,如:

  

 

2月22日周二

拼的时候再次出现这种情况:

稍微调整就能得到:

2022年3月

2022年3月1日-3月31日的答案分别是:

3月1日周二
  1  1  0  2  2  2  0
  1  1  2  2  3  3  0
  0  1  6  4  4  3  5
  6  6  6  7  4  3  5
  6  8  9  7  4  3  5
  8  8  9  7  7  7  5
  8  9  9  9 10  0 10
  0  0  0  0 10 10 10
3月2日周三
  1  2  0  3  4  4  0
  1  2  2  3  3  4  0
  1  0  2  3  3  4  4
  1  1  2  5  5  5  5
  6  7  7  7  8  8  8
  6  7  9  9  8 10  8
  6  6  6  9  9 10  0
  0  0  0  0 10 10 10
3月3日周四
  1  2  0  2  5  5  0
  1  2  2  2  5  3  0
  1  4  0  5  5  3  3
  1  4  4  4  4  7  3
  6  6  6  7  7  7  9
  6  8  8  8  9  9  9
  6  8  8 10 10 10  9
  0  0  0  0  0 10 10
3月4日周五
  1  1  0  10  10  10  0
  3  1  1  1  2  10  0
  3  3  3  0  2  10  7
  3  4  6  6  2  2  7
  4  4  6  5  5  5  7
  4  6  6  5  5  7  7
  8  8  8  8  9  9  9
  0  0  0  0  9  0  9
3月5日周六
  1  2  0  2  3  3  0
  1  2  2  2  3  3  0
  1  4  4  4  0  3  9
  1  5  4  6  6  6  9
  7  5  4  6  8  8  9
  7  5  5  6  8  9  9
  7  7  5 8 8 10 10
  0  0  0  0 10 10  0
3月6日周日
  1  2  0  2  3  3  0
  1  2  2  2  3  3  0
  1  1  4  4  3  0  7
  5  1  4  6  7  7  7
  5  8  4  6  6  6  7
  5  8  4 10 10  6 9
  5  8  8 0 10 10 9
  0  0  0  0 9 9 9
3月7日周一
  1  1  0  2  2  2  0
  1  1  2  2  3  3  0
  1  5  4  4  4  3  0
  5  5  4  6  4  3  3
  5  6  6  6  7  7  7
  8  9  9  9  9 10  7
  8  8  8  8  0 10  7
  0  0  0  0 10 10 10
3月8日周二
  1  1  0  2  3  3  0
  1  1  1  2  2  3  0
  4  4  4  4  2  3  3
  0  5  5  5  6  6  6
  7  5  8  8  8  8  6
  7  7  7  9  9  8  6
  7  9  9  9 10  0 10
  0  0  0  0 10 10 10

3月9日周三
  1  1  0  2  2  3  0
  1  2  2  2  3  3  0
  1  1  7  7  3  3  4
  5  0  7  6  6  6  4
  5  7  7  6  8  8  4
  5  9  9  6  8 10  4
  5  5  9  9  8 10  0
  0  0  0  0 10 10 10
3月10日周四
  1  1  0  2  2  2  0
  3  1  1  2  5  5  0
  3  3  3  2  5  9  4
  3  6  0  5  5  9  4
  6  6  7  7  7  9  4
  6  8  7  8  9  9  4
  6  8  8  8 10 10 10
  0  0  0  0  0 10 10
3月11日周五
  1  1  0  2  3  3  0
  4  1  1  2  2  3  0
  4  4  4  2  2  3  3
  4  7  5  0  6  6  6
  7  7  5  5  5  5  6
  7  8  9  9  9  9  6
  7  8  8  8 10 10 10
  0  0  0  0 10  0 10
3月12日周六
  1  2  0  2  3  3  0
  1  2  2  2  5  3  0
  1  4  5  5  5  3  3
  1  4  4  6  0  8  8
  7  4  4  6  8  8 10
  7  7  7  6  6  6 10
  7  9  9  9  9 10 10
  0  0  0  0  9 10  0
3月13日周日
  1  2  0  2  3  3  0
  1  2  2  2  4  3  0
  1  1  4  4  4  3  3
  5  5  5  5  4  0  7
  5  6  6  6  6  7  7
  8  8  9  9  9  7 10
  8  8  8  0  9  9 10
  0  0  0  0 10 10 10
3月14日周一
  1  2  0  2  3  3  0
  1  2  2  2  4  3  0
  1  1  1  4  4  3  3
  5  6  6  4  9  7  0
  5  6  8  9  9  7  7
  5  6  8  9  9 10  7
  5  6  8  8  0 10  7
  0  0  0  0 10 10 10
3月15日周二
  1  1  0  2  3  3  0
  1  1  1  2  3  4  0
  5  5  5  2  3  4  4
  5  7  5  2  3  6  4
  0  7  9  6  6  6  8
  7  7  9  6  8  8  8
  9  9  9 10 10  0  8
  0  0  0  0 10 10 10
3月16日周三
  1  2  0  2  3  3  0
  1  2  2  2  4  3  0
  1  1  1  4  4  3  3
  5  5  8  4  6  7  7
  5  0  8  9  6  7  7
  5  8  8  9  6 10  7
  5  8  9  9  6 10  0
  0  0  0  0 10 10 10
3月17日周四
  1  2  0  2  3  3  0
  1  2  2  2  4  3  0
  1  1  7  5  4  3  3
  6  7  7  5  4  4  4
  6  7  0  5  5  8  8
  6  9  9  9  5 10  8
  6  9  9 10 10 10  8
  0  0  0  0  0 10  8

3月18日周六
  1  2  0  6  3  3  0
  1  2  2  6  4  3  0
  1  5  2  6  4  3  3
  1  5  6  6  4  4  4
  7  5  5  0  8  8  8
  7  7  7  9  9  8  8
  7  9  9  9 10 10 10
  0  0  0  0 10  0 10
3月19日周六
  1  2  0  2  3  3  0
  1  2  2  2  5  3  0
  1  4  5  5  5  3  3
  1  4  4  6  5  7  7
  8  8  4  6  0  7  7
  8  9  4  6  6  6  7
  8  9  9  9  9 10 10
  0  0  0  0 10 10  0
3月20日周日
  1  2  0  2  3  3  0
  1  2  2  2  5  3  0
  1  4  4  5  5  3  3
  1  4  10 5  8  8  8
  7  4  10 8  8  0 6
  7  4 10 10 6 6 6
  7  7  7  0 9 9 6
  0  0  0  0 9 9 9
3月21日周一
  1  2  0  2  3  3  0
  1  2  2  2  5  3  0
  1  4  4  5  5  3  3
  1  1  4  5  7  7  7
  6  6  4  7  7  8  0
  6  6  6  8  8  8 10
  9  9  9  9  0  8 10
  0  0  0  0 10 10 10
3月22日周二
  1  2  0  2  3  3  0
  1  2  2  2  4  3  0
  1  1  1  4  4  3  3
  5  5  5  4  8  8  6
  5  9  7  8  8  8  6
  0  9  7  7  7  7  6
  9  9  9 10 10  0  6
  0  0  0  0 10 10 10
3月23日周三
  1  2  0  3  4  4  0
  1  2  2  3  3  4  0
  1  1  2  3  3  4  4
  5  5  5  5  6  6  6
  5  7  7  7  7  9  6
  8  0  8  9  9  9  6
  8  8  8 10 10  9  0
  0  0  0  0 10 10 10
3月24日周四
  1  2  0  2  3  3  0
  1  2  2  2  5  3  0
  1  4  4  4  5  3  3
  1  4  5  5  5  7  7
  6  6  6  6  7  7  9
  8  8  0  6  9  9  9
  8  8  8 10 10 10  9
  0  0  0  0  0 10 10
3月25日周五
  1  2  0  2  3  3  0
  1  2  2  2  4  3  0
  1  7  5  5  4  3  3
  1  7  5  6  4  4  4
  7  7  5  6  6  6  6
  7  9  9  0 10  8  8
  9  9 10 10 10  8  8
  0  0  0  0 10  0  8
3月26日周六
  1  2  0  2  3  3  0
  1  2  2  2  4  3  0
  1  1  4  4  4  3  3
  5  5  5  5  4  6  6
  7  8  8  9  9  9  6
  7  7  8  8  0  9  6
  7  7 10 10 10  9  6
  0  0  0  0 10 10  0
3月27日周日
  1  2  0  2  3  3  0
  1  2  2  2  4  3  0
  1  1  6  7  4  3  3
  5  6  6  7  4  4  4
  5  6  7  7  7  8  8
  5  6 10 10  9  0  8
  5 10 10  0  9  9  8
  0  0  0  0  9  9  8
3月28日周一
  1  2  0  3  3  3  0
  1  2  2  2  2  3  0
  1  1  4  5  5  3  7
  6  1  4  5  7  7  7
  6  6  4  5  5  9  7
  8  6  4  9  9  9  0
  8  8  8  9  0 10 10
  0  0  0  0 10 10 10
3月29日周二
  1  2  0  2  3  3  0
  1  2  2  2  4  3  0
  1  5  5  5  4  3  3
  1  7  6  5  4  4  4
  7  7  6  6  6  6  9
  7  8  8  8  9  9  9
  0  8  8 10 10  0  9
  0  0  0  0 10 10 10
3月30日周三
  1  2  0  2  3  3  0
  1  2  2  2  4  3  0
  1  5  5  6  4  3  3
  1  5  6  6  4  4  4
  7  5  6  8  8  8  8
  7  7  7  9  9  9  8
  7  0  9  9 10 10  0
  0  0  0  0 10 10 10

3月31日周四
  1  2  0  2  3  3  0
  1  2  2  2  4  3  0
  1  4  4  4  4  3  3
  1  5  5  5  6  6  6
  7  7  8  5  6  6  9
  7  8  8  5  9  9  9
  7  8  0 10 10 10  9
  0  0  0  0  0 10 10

竖条下滑问题

因为是平放在桌面上拼的,所以经常忘了竖条下面不能放空格,然后每次我都会重新拼。

2月5日周六

 

3月9日周三

4月2日周六

2022年4月

4月1日周五
  1  1  2  0  3  3  0
  1  1  2  2  2  3  0
  0  1  2  5  5  3  3
  4  5  5  5  6  6  6
  4  8  9  9  9  9  6
  4  8  8 10 10 10  6
  4  4  8 10  7  7  7
  0  0  0  0  7  0  7
4月2日周六
  1  1  2  0  2  3  0
  1  1  2  2  2  3  0
  1  0  4  4  3  3  3
  4  4  4  5  8  8  8
  6  6  6  5  5  5  8
  6  9  9  9  9  5 10
  6  7  7  7  7 10 10
  0  0  0  0  7 10  0
4月3日周日
  1  1  1  0  2  2  0
  8  1  3  3  3  2  0
  8  1  0  3  3  2  2
  8  4  4  4  5  6  6
  8  4  9  9  5  5  6
  7  4  7  9  9  5  6
  7  7  7  0 10  5  6
  0  0  0  0 10 10 10

4月4日周一
  6  6  1  0  1  2  0
 7  6  1  1  1  2  0
  7  6  6 0  2  2  2
  7  7  7  4  4  8  9
  3  4  4  4  8  8  9
  3  3  3  3  8  9  9
 10 10 10 10  0  5  5
  0  0  0  0  5  5  5
4月5日周二
  8  1  1  0  2  2  0
  8  3  1  1  1  2  0
  8  3  3  3  0  2  2
  8  3 10 10  4  4  5
  9 10 10  4  4  4  5
  9  9  9  6  5  5  5
  6  6  6  6  7  0  7
  0  0  0  0  7  7  7
4月6日周三
  1  1  1  0  3  3  0
  1  2  2  3  3  3  0
  1  4  2  2  2  0  5
  8  4  4  4  5  5  5
  8  9  9  4  6  6  5
  8  8  9  9  6  7  7
 10 10 10 10  6  7  0
  0  0  0  0  6  7  7

4月7日周四
  1  1  2  0  2  4  0
  3  1  2  2  2  4  0
  3  1  1  4  4  4  0
  3  3  5  5  5  8  8
  6  3  5  5  8  8  7
  6  6  6  9  9  9  7
  6 10 10 10 10  9  7
  0  0  0  0  0  7  7
4月8日周五
  1  1  2  0  2  4  0
  1  1  2  2  2  4  0
  1  5  5  3  4  4  4
  0  5  7  3  3  3  3
  5  5  7  6  8  8  8
  7  7  7  6  6  9  8
 10 10 10 10  6  9  9
  0  0  0  0  6  0  9

4月9日周六
  1  1  2  0  2  4  0
  1  3  2  2  2  4  0
  1  3  3  3  4  4  4
  1  0  8  3  6  6  6
  5  8  8  6  6  9  9
  5  8 10 10 10 10  9
  5  5  5  7  7  7  9
  0  0  0  0  7  7  0

4月10日周日
  1  1  2  0  2  4  0
  1  3  2  2  2  4  0
  1  3  3  3  4  4  4
  1  6  0  3  5  5  5
  6  6  8  8  8  8  5
  6  7  7  7  9  9  5
  6  7  7  0 10  9  9
  0  0  0  0 10 10 10

4月11日周一
  1  1  2  0  3  3  0
  1  1  2  2  2  3  0
  7  1  2 10  10  3  3
  7  7  4 0 10 10 10
  5  7  4  4  4  4  8
  5  9  9  9  6  6  8
  5  5  5  9  0  6  8
  0  0  0  0  6  6  8
4月12日周二
  1  1  2  0  3  3  0
  1  1  2  2  2  3  0
  4  1  2  5  5  3  3
  4  5  5  5  0  8  8
  4  4  4  6  8  8  9
  6  6  6  6  9  9  9
 10 10 10 10  7  0  7
  0  0  0  0  7  7  7
4月13日周三
  1  1  2  0  2  3  0
  8  1  2  2  2  3  0
  8  1  1  5  4  3  3
  8  5  5  5  4  0  3
  8 10 10  5  4  4  4
 10 10  9  6  6  6  6
  9  9  9  6  7  7  0
  0  0  0  0  7  7  7

4月14日周四
  1  1  1  0  2  2  0
  1  3  3  3  4  2  0
  1  7  3  8  4  2  2
  5  7  3  8  4  4  0
  5  7  7  8  6  4  6
  5  9  9  8  6  6  6
  5  5  9  9 10 10 10
  0  0  0  0 10 10  10
4月15日周五
  1  1  8  0  2  2  0
  1  3  8  8  4  2  0
  1  3  3  8  4  2  2
  1  6  3  9  4  4  4
  0  6  3  9  5  5  5
  6  6  6  9  9  5  5
 10 10 10 10  7  7  7
  0  0  0  0  7  0  7

4月16日周六
  1  1  2  0  2  4  0
  1  3  2  2  2  4  0
  1  3  3  3  4  4  4
  1  5  5  3  8  8  8
  6  0  5  5  5  9  8
  6 10 10 10 10  9  9
  6  6  6  7  7  7  9
  0  0  0  0  7  7  0

4月17日周日
  8  1  2  0  2  3  0
  8  1  2  2  2  3  0
  8  1  9  9  3  3  3
  8  1  1  9  9  5  5
  4 10  0  5  5  5  6
  4 10 10 10  6  6  6
  4  4  4  0  6  7  7
  0  0  0  0  7  7  7
4月18日周一
  1  1  2  0  2  3  0
 10  1  2  2  2  3  0
 10  1  1  3  3  3  4
 10  10  7  4  4  4  4
  5 10  7 0  9  8  8
  5  5  7  9  9  6  8
  5  5  7  9  0  6  8
  0  0  0  0  6  6  6

4月19日周二
  1  1  2  0  2  4  0
  1  3  2  2  2  4  0
  1  3  3  3  4  4  4
  1  5  5  3  8  8  8
  6  5  5  5  0  7  8
  6  9  9  9  9  7  7
  6  6  6 10 10  0  7
  0  0  0  0 10 10  7

4月20日周三
  1  1  2  0  2 10  0
  1  7  2  2  2  10  0
  1  7  7  7 10 10 10
  1  8  9  9  9  9  5
  3  8  8  6  6 0  5
  3  3  8  6  4  5  5
  3  3  6  6  4  5  0
  0  0  0  0  4  4  4
4月21日周四
  7  1  2  0  2  3  0
  7  1  2  2  2  3  0
  7  1  1  1  4  3  3
  7  4  4  4  4 10  3
  8  8  9  5  5  10 0
  8  9  9  5 10 10 10
  8  9  5  5  6  6  6
  0  0  0  0  0  6  6

4月22日周五
  1  2  2  0  3  3  0
  1  2  2  2  4  3  0
  1  1  5  5  4  3  3
  7  1  5  6  4  4  4
  7  7  5  6  6  6  8
  0  7  5  6  8  8  8
  9  9  9  9 10 10 10
  0  0  0  0 10  0 10

4月23日周六
  8  1  2  0  2  3  0
  8  1  2  2  2  3  0
  8  1  1  1  3  3  3
  8  5  7  4  9  9  9
  5  5  7  4  4  4  9
  5  0  7  6  6  4 10
  5  7  7  6  6 10 10
  0  0  0  0  6 10  0

4月24日周日
  1  2  3  0  3  4  0
  1  2  3  3  3  4  0
  1  2  2  2  4  4  4
  1  1  5  5  5  8  8
  9  9  9  9  5  5  8
  6  6  0  7  7 10  8
  6  6  6  0  7 10 10
  0  0  0  0  7  7 10

4月25日周一
  8  1  1  0  2  2  0
  8  8  1  1  1  2  0
  9  8  3  3  3  2  2
  9  6  3  3  4  4  4
  9  6  5  5  4 10 10
  9  6  5  0  4  7 10
  6  6  5  5  0  7 10
  0  0  0  0  7  7  7

4月26日周二
  1  1  2  0  3  3  0
  1  1  2  2  2  3  0
  4  1  2  5  5  3  3
  4  5  5  5  8  9  9
  4  4  4  6  8  8  9
  6  6  6  6  0  8  9
 10 10 10 10  7  0  7
  0  0  0  0  7  7  7

4月27日周三
  8  8  1  0  1  2  0
  9  8  1  1  1  2  0
  9  8 10 10  2  2  2
  9 10 10  4  3  3  3
  9  4  4  4  6  6  3
  5  4  6  6  6  0  3
  5  5  5  5  7  7  0
  0  0  0  0  7  7  7

4月28日周四
  1  1  2  0  2  5  0
  3  1  2  2  2  5  0
  3  1  1  4  5  5  5
  3  3  3  4  4  4  4
  8  8 10  9  9  9  9
  8 10 10  6  6  6  0
  8 10  6  6  7  7  7
  0  0  0  0  0  7  7

4月29日周五
  1  1  1  0  2  2  0
  1  3  3  3  3  2  0
  1  9  9  8  3  2  2
  9  9  4  8  5  5  5
  4  4  4  8 10  5  5
  6  6  4  8 10 10 10
  0  6  6  6  7  7  7
  0  0  0  0  7  0  7
4月30日周六
  1  1  2  0  2  5  0
  1  1  2  2  2  5  0
  1  3  3  4  5  5  5
  8  6  3  4  4  4  4
  8  6  3  3  9  9  9
  8  6  6  6  7  7  9
  8  0  7  7  7 10 10
  0  0  0  0 10 10  0

三,术语

可行的匹配:把某个块放在某个位置之后,如果接下来还有解,那就是可行的匹配。

平坦区域:接近矩形的区域,没有十分明显的局部特征。

四,启发式搜索策略

1,数独

我构造了一个数独来显化这种启发式策略:

对于这个数独,没有任何疑问,首先看行填2,其次看列填5,然后看宫填9。

 现在我们来总结一下这个思维的本质。

有些人可能会这么描述:“先把确定的填了,再看不确定的”,或者“先把简单的填了,再看难的”。

这些说法没错,但是不够精确。

在一开始,单独看3个格子中的任意一个的话,E2有3种情况(看宫),分别是259,F2有2种情况(看列),分别是25,F3有1种情况(看行),是2

那么,在这个深度优先搜索问题中,我们的策略是,先把元素进行排序,情况少的元素往前排,即先搜索情况比较少的元素。这是深度优先搜索的常见优化技巧。

2,策略一

如果对于结构比较复杂的局部区域,有某种拼法是比较精巧的,那这就很可能是某个正确答案的一部分

  

这样一条模糊的策略,是否正确?本质是啥?

首先,结构比较复杂的局部区域是问题的一种特征,精巧的覆盖是解的一种特征

问题的特征告诉我们,这个位置的可能的情况比较少,而平坦位置的可能情况比较多。

解的特征告诉我们,有相对较高的概率这个一个可行的匹配,如果确实是一个可行匹配,那么在此匹配前提下的解是相对较多的。

也就是说,这其实暗含了下面列的很多条策略,没想到吧O(∩_∩)O哈哈~

3,策略二

先从结构比较复杂的局部区域开始拼,平坦的区域靠后拼

此时我们和上面的数独对比,可以感受到,他们的核心逻辑是完成相同的。

通常情况下,被排除的3个格子、右上角、右下角都是结构比较复杂的局部区域。

4,策略三

我们在拼的时候经常需要评估,一个匹配的优劣程度。在面临选择时,我们优先选择直觉上更优的匹配

直觉有点玄乎,但是我们可以看到我在上面给出的三月的解法有很多都是这样的:

可以说这就是很优的匹配。

PS:如果我一早就想通了这个策略,3月至少有20天这2个块就可以这么放。

对于4月,我相信这是一个不错的匹配:

5,策略四

先从复杂的块开始拼,简单的块靠后

这个和策略二很相似,策略二其实是,复杂的局部区域有更少的可能性,策略四其实是,复杂的块有更少的可能性。

以下图为例感受一下简单块的可能性之多:

 即使到了最后一步,仍然有2种放法。

而如果留到最后的是复杂的块,就很可能是0-1种放法。

根据块的复杂程度,我简单分成三个梯度,复杂,中等,简单。

3个复杂的:

5个中等的:

2个简单的:

6,策略五

首先我们分析一下这个拼图的平直程度。

例子太多,我不一一列举。

我直接给出我总结出的规律,在同类puzzle中,日历拼图的平直程度是非常高的

 为什么会这样呢?我认为主要是块本身的属性造成的。

我把日历拼图的10个块,在方格游戏中的17个格子中标示出来(去掉小于4个格子的只有17个)

1、3、4、7、11、14、16号这7个是没有的,而其中的1、3、4、7、14显然符合我在上面策略四中提到的“复杂的块”这个特征。

从某种程度上说,这个复杂其实说的就是不平直的程度。

日历拼图和方格游戏是两个极端日历拼图为了每个组合都有解,选的主要是平直度较高的块,方格游戏其实不是覆盖问题,而是依赖角点进行区域拓宽的一种博弈游戏,所以选的主要是平直度较低的块

上面的策略四是根据块的复杂性(平直度)进行排序,而这里的策略五只针对平直度低的块,以3月29日周二为例:

周二这个格子和L型的块是一个很差的匹配。

而这是一个非常好的匹配,这个块和边界完美贴合,和周二的匹配也非常好。

综上,策略五就是,月日周排除的三个格子,尤其是离边界还有一个距离的格子(如上周二),要放在块的折口处,但是不能放L型块的折口处,从而维持整个局面的平直性

7,策略六

策略六是替换策略。

以2月4日周五为例,我拼成了这样:

乍一看无解,实际上答案已经出来了:

一般来说,最简单的替换是这2组:

五,数字化

利用边缘检测和其他图像处理技术,把一张包含答案的图片,转化成数字。

1,读取图片并二值化

Mat GetImage(int i)
{
	Mat img = imread("D:/p/img (" + to_string(i) + ").png", 0);
	resize(img, img, Size(0, 0), 0.5, 0.5);
	Mat src;
	threshold(img, src, 200, 255, THRESH_TRUNC);
	threshold(src, src, 100, 255, THRESH_TOZERO);
	return src;
}

2,边缘检测

	Mat src = GetImage(i);
	Canny(src, src, 200, 100, 3);
	cv::imshow("src" + to_string(i), src);

3,轮廓检测

    std::vector<std::vector<Point>> contours;
	std::vector<Vec4i> hierarchy;
	findContours(src, contours, hierarchy, RETR_LIST, CHAIN_APPROX_NONE, Point(0, 0));
	cout << contours.size() << endl;
	sort(contours.begin(), contours.end(), cmp< Point>);
	for (int i = 0; i < contours.size(); i++)cout << contours[i].size() << " ";

把轮廓按照点数排序,点最多的轮廓就是我们需要的轮廓。

4,求解格子尺寸、坐标

int GetSize(const vector<std::vector<Point>>& contours, int &xmin ,int &ymin)
{
	xmin = 1234567, ymin = 1234567;
	int xmax = 0, ymax = 0;
	for (int i = 0; i < 5 && i < contours.size(); i++) {
		for (auto& pi : contours[i]) {
			if (xmin > pi.x)xmin = pi.x;
			if (ymin > pi.y)ymin = pi.y;
			if (xmax < pi.x)xmax = pi.x;
			if (ymax < pi.y)ymax = pi.y;
		}
	}
	int dx = xmax - xmin, dy = ymax - ymin;
	dx /= 7, dy /= 8;
	cout << endl << dx << endl << dy;
	return dx;
}

其中for循环控制的是取前多少个轮廓,否则很容易受到最外面的轮廓的影响。

xmin和ymin记录了左上角的格子坐标。

5,计算有效轮廓数量

为了更有效的计算格子尺寸,需要更智能的选择轮廓数量。

void GetContoursNum(Mat src, const vector<std::vector<Point>>& contours)
{
	bool xmin=false, xmax = false, ymin = false, ymax = false;
	contoursNum = 0;
	for (contoursNum = 0; contoursNum < contours.size(); contoursNum++) {
		for (auto &pi : contours[contoursNum]) {
			if (src.cols / 7 > pi.x)xmin = true;
			if (src.rows / 8 > pi.y)ymin = true;
			if (src.cols / 7*6 < pi.x)xmax = true;
			if (src.rows / 8*7 < pi.y)ymax = true;
		}
		if (xmin && ymin && xmax && ymax)break;
	}
	contoursNum++;
}

这对于一些场景有帮助,但是对于最外面的干扰轮廓很清晰的情况,还是无法避免。

6,坐标微调

计算的坐标还是容易受最外面的轮廓的影响,所以我们把坐标进行微调。

试了几个方法都不太好,先不做这个了,留一个接口,如果需要的话可以手动调。

void GetPos()
{
	xmin += 0, ymin += 0; // 手动调整
	return;
}

7,手动删减轮廓

最外面的轮廓干扰太大,所以最后我干脆提供一个手动删掉几个轮廓的接口。

void RemoveContours(vector<std::vector<Point>>& contours)
{
	int id[] = { 2,3 }; // 手动调整
	for (int i = sizeof(id) / sizeof(int); i >= 0; i--) {
		if (id[i] < contours.size())contours.erase(contours.begin() + id[i]);
	}
}

手动调坐标不太好操作,而且不精确,但根据显示的图很容易找出最外面的1-2个轮廓(如果有的话),这样就可以直接手动填写要删除的轮廓id重新运行。

8,解析空出来的3个格子

void GetInvalidPos(int size, Mat src)
{
	int x[8][7];
	int m, d, w, k = 0;
	for (int i = 0; i < 8; i++)for (int j = 0; j < 7; j++) {
		x[i][j] = 0;
		if (i < 2 && j == 6)continue;
		if (i == 7 && j < 4)continue;
		int r = ymin + size * i;
		int c = xmin + size * j;
		for (int row = r + size / 4; row < r + size / 4 * 3; row++) {
			for (int col = c + size / 4; col < c + size / 4 * 3; col++) {
				x[i][j] += int(src.at<uchar>(row, col));
			}
		}
		if (x[i][j] > 100) {
			if (k == 0) {
				m = i * 6 + j + 1; // 1-12
			} else if (k == 1) {
				d = (i - 2) * 7 + j + 1; // 1-31
			} else {
				w = (i - 6) * 3 + j - 3; // 0-6
			}
			k++;
		}
	}
	string s[] = { "日","一","二" ,"三" ,"四" ,"五" ,"六" };
	cout << m << "月" << d << "日周" << s[w];
}

8,连通性计算

void connect(Mat src)
{

	//for (int i = 0; i < src.rows; i++)for (int j = 0; j < src.cols; j++) {
	//	if (i%size_ == ymin % size_ || j % size_ == xmin % size_) {
	//		src.at<uchar>(i,j) = 200;
	//	}
	//}
	//imshow("src", src);

	bool up[8][7];
	for (int i = 1; i < 8; i++)for (int j = 0; j < 7; j++) {
		up[i][j] = true;
		if (!valid(i,j))continue;
		if (!valid(i-1, j))continue;
		int s = 0;
		//src.at<uchar>(ymin + size_ * i - size_ / 4, xmin + size_ * j + size_ / 4) = 200;
		//src.at<uchar>(ymin + size_ * i + size_ / 4, xmin + size_ * j + size_ / 4 * 3) = 200;
		for (int r = ymin + size_ * i - size_ / 4; r < ymin + size_ * i + size_ / 4; r++) {
			for (int c = xmin + size_ * j + size_ / 4; c < xmin + size_ * j + size_ / 4 * 3; c++) {
				if (int(src.at<uchar>(r, c)) > 10)s++;
			}
		}
		if (s > 5)up[i][j] = false;
	}
	//imshow("src", src);
	bool left[8][7];
	for (int i = 0; i < 8; i++)for (int j = 1; j < 7; j++) {
		left[i][j] = true;
		if (!valid(i, j))continue;
		if (!valid(i, j-1))continue;
		int s = 0;
		for (int r = ymin + size_ * i + size_ / 4; r < ymin + size_ * i + size_ / 4*3; r++) {
			for (int c = xmin + size_ * j - size_ / 4; c < xmin + size_ * j + size_ / 4; c++) {
				if (int(src.at<uchar>(r, c)) > 10)s++;
			}
		}
		if (s > 10)left[i][j] = false;
	}
	return;
}

计算每2个格子间的交接处有没有边缘检测出的点,判断2个格子是否连通。

再用并查集把连通的格子连起来。

bool valid2(int i, int j)
{
	if (!valid(i,j))return false;
	if (i == mi && j == mj)return false;
	if (i == di && j == dj)return false;
	if (i == wi && j == wj)return false;
	return true;
}
int fa[8 * 7]; // id=i*7+j
int find(int x)	//找祖先
{
	if (fa[x] == x)return x;
	return fa[x] = find(fa[x]);
}
void split()
{
	for (int i = 0; i < 56; i++)fa[i] = i;
	for (int i = 1; i < 8; i++)for (int j = 0; j < 7; j++) {
		if (!valid2(i, j))continue;
		if (!valid2(i - 1, j))continue;
		if (up[i][j])fa[find(i * 7 + j)] = find((i - 1) * 7 + j);
	}
	for (int i = 0; i < 8; i++)for (int j = 1; j < 7; j++) {
		if (!valid2(i, j))continue;
		if (!valid2(i, j-1))continue;
		if (left_[i][j])fa[find(i * 7 + j)] = find(i * 7 + j - 1);
	}
	map<int, int>m;
	for (int i = 0; i < 8; i++)for (int j = 0; j < 7; j++) {
		if (!valid2(i, j))continue;
		m[find(i * 7 + j)]++;
	}
	int k = 0;
	map<int, int>m2;
	for (auto &mi : m)m2[mi.first] = ++k;
	int block[8][7];
	for (int i = 0; i < 8; i++) {
		for (int j = 0; j < 7; j++) {
			if (!valid2(i, j))block[i][j] = 0;
			else block[i][j] = m2[find(i * 7 + j)];
			cout <<setw(3)<< block[i][j];
		}
		cout << endl;
	}
	return;
}

9,完整代码



#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
#include<map>
#include<opencv2/opencv.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/core/mat.hpp>
using namespace std;
using namespace cv;


#pragma comment(lib,"../x64/vc14/lib/opencv_world452.lib")
#pragma comment(lib,"../x64/vc14/lib/opencv_world452d.lib")



int xmin, ymin, size_;
int contoursNum;
bool up[8][7];
bool left_[8][7];
int mi, mj, di, dj, wi, wj;

template<typename T>
bool cmp(vector<T> x, vector<T> y)
{
	return x.size() > y.size();
}

Mat GetImage(int i)
{
	Mat img = imread("D:/p/img (" + to_string(i) + ").jpg", 0);
	resize(img, img, Size(0, 0), 0.3, 0.3);
	Mat src;
	threshold(img, src, 200, 255, THRESH_TRUNC);
	threshold(src, src, 100, 255, THRESH_TOZERO);
	return src;
}
void RemoveContours(vector<std::vector<Point>>& contours)
{
	int id[] = { 100 }; // 手动调整
	for (int i = sizeof(id) / sizeof(int) -1; i >= 0; i--) {
		if (id[i] < contours.size())contours.erase(contours.begin() + id[i]);
	}
}
void GetContoursNum(Mat src, const vector<std::vector<Point>>& contours)
{
	bool xmin = false, xmax = false, ymin = false, ymax = false;
	contoursNum = 0;
	for (contoursNum = 0; contoursNum < contours.size(); contoursNum++) {
		for (auto& pi : contours[contoursNum]) {
			if (src.cols / 7 > pi.x)xmin = true;
			if (src.rows / 8 > pi.y)ymin = true;
			if (src.cols / 7 * 6 < pi.x)xmax = true;
			if (src.rows / 8 * 7 < pi.y)ymax = true;
		}
		if (xmin && ymin && xmax && ymax)break;
	}
	contoursNum++;
}
void GetSize(const vector<std::vector<Point>>& contours)
{
	xmin = 1234567, ymin = 1234567;
	int xmax = 0, ymax = 0;
	for (int i = 0; i < contoursNum && i < contours.size(); i++) {
		for (auto& pi : contours[i]) {
			if (xmin > pi.x)xmin = pi.x;
			if (ymin > pi.y)ymin = pi.y;
			if (xmax < pi.x)xmax = pi.x;
			if (ymax < pi.y)ymax = pi.y;
		}
	}
	int dx = xmax - xmin, dy = ymax - ymin;
	dx /= 7, dy /= 8;
	//cout << endl << dx << endl << dy;
	size_ = dx;
}

void GetPos()
{
	xmin += 0, ymin += 0; // 手动调整
	return;
}

bool valid(int i, int j)
{
	if (i < 2 && j == 6)return false;
	if (i == 7 && j < 4)return false;
	return true;
}

void GetInvalidPos(Mat src)
{
	int x[8][7];
	int m, d, w, k = 0;
	for (int i = 0; i < 8; i++)for (int j = 0; j < 7; j++) {
		x[i][j] = 0;
		if (!valid(i, j))continue;
		int r = ymin + size_ * i;
		int c = xmin + size_ * j;
		for (int row = r + size_ / 4; row < r + size_ / 4 * 3; row++) {
			for (int col = c + size_ / 4; col < c + size_ / 4 * 3; col++) {
				x[i][j] += int(src.at<uchar>(row, col));
			}
		}
		if (x[i][j] > 2000) {
			if (k == 0) {
				mi = i, mj = j;
				m = i * 6 + j + 1; // 1-12
			}
			else if (k == 1) {
				di = i, dj = j;
				d = (i - 2) * 7 + j + 1; // 1-31
			}
			else {
				wi = i, wj = j;
				w = (i - 6) * 3 + j - 3; // 0-6
			}
			k++;
		}
	}
	if (w < 0 || w>7) {
		cout << "error,w=" << w << endl;
		w = 0;
	}
	string s[] = { "日","一","二" ,"三" ,"四" ,"五" ,"六" };
	cout << m << "月" << d << "日周" << s[w] << endl;
}

void connect(Mat src)
{

	//for (int i = 0; i < src.rows; i++)for (int j = 0; j < src.cols; j++) {
	//	if (i%size_ == ymin % size_ || j % size_ == xmin % size_) {
	//		src.at<uchar>(i,j) = 200;
	//	}
	//}
	//imshow("src", src);


	for (int i = 1; i < 8; i++)for (int j = 0; j < 7; j++) {
		up[i][j] = true;
		if (!valid(i, j))continue;
		if (!valid(i - 1, j))continue;
		int s = 0;
		//src.at<uchar>(ymin + size_ * i - size_ / 4, xmin + size_ * j + size_ / 4) = 200;
		//src.at<uchar>(ymin + size_ * i + size_ / 4, xmin + size_ * j + size_ / 4 * 3) = 200;
		for (int r = ymin + size_ * i - size_ / 4; r < ymin + size_ * i + size_ / 4; r++) {
			for (int c = xmin + size_ * j + size_ / 4; c < xmin + size_ * j + size_ / 4 * 3; c++) {
				if (int(src.at<uchar>(r, c)) > 10)s++;
			}
		}
		if (s > 5)up[i][j] = false;
	}
	//imshow("src", src);

	for (int i = 0; i < 8; i++)for (int j = 1; j < 7; j++) {
		left_[i][j] = true;
		if (!valid(i, j))continue;
		if (!valid(i, j - 1))continue;
		int s = 0;
		for (int r = ymin + size_ * i + size_ / 4; r < ymin + size_ * i + size_ / 4 * 3; r++) {
			for (int c = xmin + size_ * j - size_ / 4; c < xmin + size_ * j + size_ / 4; c++) {
				if (int(src.at<uchar>(r, c)) > 10)s++;
			}
		}
		if (s > 10)left_[i][j] = false;
	}
	return;
}

bool valid2(int i, int j)
{
	if (!valid(i, j))return false;
	if (i == mi && j == mj)return false;
	if (i == di && j == dj)return false;
	if (i == wi && j == wj)return false;
	return true;
}
int fa[8 * 7]; // id=i*7+j
int find(int x)	//找祖先
{
	if (fa[x] == x)return x;
	return fa[x] = find(fa[x]);
}
void split()
{
	for (int i = 0; i < 56; i++)fa[i] = i;
	for (int i = 1; i < 8; i++)for (int j = 0; j < 7; j++) {
		if (!valid2(i, j))continue;
		if (!valid2(i - 1, j))continue;
		if (up[i][j])fa[find(i * 7 + j)] = find((i - 1) * 7 + j);
	}
	for (int i = 0; i < 8; i++)for (int j = 1; j < 7; j++) {
		if (!valid2(i, j))continue;
		if (!valid2(i, j - 1))continue;
		if (left_[i][j])fa[find(i * 7 + j)] = find(i * 7 + j - 1);
	}
	map<int, int>m;
	for (int i = 0; i < 8; i++)for (int j = 0; j < 7; j++) {
		if (!valid2(i, j))continue;
		m[find(i * 7 + j)]++;
	}
	using P = pair<int, int>;
	vector<P>v;
	for (auto& mi : m)v.push_back(mi);
	sort(v.begin(), v.end(), [](P p1, P p2) { return p1.second > p2.second; });
	int k = 0;
	map<int, int>m2;
	for (auto& vi : v)m2[vi.first] = ++k;
	int block[8][7];
	int pix = 50;
	Mat img = Mat(Size(pix * 7, pix * 8), CV_8UC1);
	for (int i = 0; i < 8; i++) {
		for (int j = 0; j < 7; j++) {
			if (!valid2(i, j))block[i][j] = 0;
			else block[i][j] = m2[find(i * 7 + j)];
			cout << setw(3) << block[i][j];
			for (int r = pix * i; r < pix * (i + 1); r++) {
				for (int c = pix * j; c < pix * (j + 1); c++) {
					img.at<uchar>(r, c) = 25 * block[i][j];
				}
			}
			if (!valid2(i, j)) {
				for (int x = 0; x < pix; x++) {
					img.at<uchar>(pix * i + x, pix * j + x) = 255;
					img.at<uchar>(pix * i + x, pix * j + pix - x -1) = 255;
				}
			}
		}
		cout << endl;
	}
	static int kid = 0;
	imshow("ans"+to_string(kid++),img);
	return;
}

void f(int i)
{
	Mat src = GetImage(i);
	Canny(src, src, 200, 100, 3);
	//cv::imshow("src" + to_string(i), src);

	std::vector<std::vector<Point>> contours;
	std::vector<Vec4i> hierarchy;
	findContours(src, contours, hierarchy, RETR_LIST, CHAIN_APPROX_NONE, Point(0, 0));
	//cout << contours.size() << endl;
	sort(contours.begin(), contours.end(), cmp< Point>);
	RemoveContours(contours);
	//for (int i = 0; i < contours.size(); i++)cout << contours[i].size() << " ";
	GetContoursNum(src, contours);

	Mat img(Size(src.cols, src.rows), src.type());
	img = 0;
	for (int i = 0; i < contoursNum && i < contours.size(); i++) {
		cv::drawContours(img, contours, i, cv::Scalar::all(255));
		//cv::imshow("contours" + to_string(i), img);
	}

	GetSize(contours);
	GetPos();
	GetInvalidPos(src);
	connect(src);
	split();
}

int main()
{
	for (int i = 7; i <= 11; i++)
	{
		f(i);
	}
	cv::waitKey(0);
	return 0;
}

运行效果:

可以看到数字化完全正确,为了方便校验做出来的灰度图也一致。

在10张照片里面有一张运行有点问题:

有2个块连起来了,这种情况只能手动微改一下了。

六,以解生解

每完成一个月的解法,就可以按照大拇指和U型分别生成解,把找到的新解存下来以作备用。

1,大拇指

把形似大拇指的这个块,通过翻转,可以生成不同的解。

(1)识别大拇指 

//#include "data.h"

#include<iostream>
#include <vector>
using namespace std;

#define OUT(x) cout << endl << #x << " = "; Print(x);
template<typename T>
inline void Print(T x)
{
	cout << x << " ";
}
template<typename T>
inline void Read(T& x)
{
	while (!(cin >> x)) { // only cin type T, ignore other info
		cin.clear();
		cin.ignore();
	}
}

const float theNan = 0.123456; //float默认只有6位
void Print(float x)
{
	if (std::isnan(x)) cout << theNan << " ";
	else cout << x << " ";
}
void Read(float& x)
{
	Read<float>(x);
	if (x == theNan) x = NAN;
}

int main()
{
	int x[8][7];
	while (true)
	{
		for (int i = 0; i < 8; i++)
		{
			for (int j = 0; j < 7; j++)
			{
				Read(x[i][j]);
			}
		}
		for (int k = 1; k < 10; k++)
		{
			int xmin = 10, ymin = 10, xmax = 0, ymax = 0;
			for (int i = 0; i < 8; i++)for (int j = 0; j < 7; j++)
			{
				if (x[i][j] != k)continue;
				xmin = min(xmin, i);
				ymin = min(ymin, j);
				xmax = max(xmax, i);
				ymax = max(ymax, j);
			}
			if ((xmax - xmin + 1) * (ymax - ymin + 1) != 6)continue;
			bool flag = true;
			int n = 0;
			for (int i = xmin; i <= xmax && flag; i++)for (int j = ymin; j <= ymax && flag; j++)
			{
				if (x[i][j] == k)continue;
				if (x[i][j])flag = false;
				n++;
			}
			if (!flag || n > 1)continue;
			if (x[xmin][ymin] && x[xmin][ymax] && x[xmax][ymin] && x[xmax][ymax])continue;
			cout << "                                  "<<k << endl;
		}
	}
	return 0;
}

(2)进行翻转

除了当前解,还可以生成3个不同的解

            for (int i = 0; i < 3; i++) {
				int tmp = x[xmin][ymin];
				x[xmin][ymin] = x[xmin][ymax];
				x[xmin][ymax] = x[xmax][ymin];
				x[xmax][ymin] = x[xmax][ymax];
				x[xmax][ymax] = tmp;

			}

(3)校验是否合法

int x[8][7];
int m, d, w;
string s[] = { "日","一","二" ,"三" ,"四" ,"五" ,"六" };
bool valid(int i, int j)
{
	if (i < 2 && j == 6)return false;
	if (i == 7 && j < 4)return false;
	return true;
}
bool check()
{
	int k = 0;
	for (int i = 0; i < 8; i++)for (int j = 0; j < 7; j++) {
		if (!valid(i, j))continue;
		if (x[i][j] == 0) {
			if (k == 0) {
				if (i > 1)
					return false;
				m = i * 6 + j + 1; // 1-12
			}
			else if (k == 1) {
				if (i <= 1)
					return false;
				if (i * 7 + j >= 45)
					return false;
				d = (i - 2) * 7 + j + 1; // 1-31
			}
			else {
				if (i * 7 + j < 45)
					return false;
				w = (i - 6) * 3 + j - 3; // 0-6
			}
			k++;
		}
	}
}

2,可视化

为了方便查看,单独把显示图像的函数提取出来。

int x[8][7];

void show()
{
	int pix = 50;
	Mat img = Mat(Size(pix * 7, pix * 8), CV_8UC1);
	for (int i = 0; i < 8; i++) {
		for (int j = 0; j < 7; j++) {
			for (int r = pix * i; r < pix * (i + 1); r++) {
				for (int c = pix * j; c < pix * (j + 1); c++) {
					img.at<uchar>(r, c) = 25 * x[i][j];
				}
			}
			if (x[i][j]==0) {
				for (int x = 0; x < pix; x++) {
					img.at<uchar>(pix * i + x, pix * j + x) = 255;
					img.at<uchar>(pix * i + x, pix * j + pix - x - 1) = 255;
				}
			}
		}
		cout << endl;
	}
	static int kid = 0;
	imshow("ans" + to_string(kid++), img);
}

int main()
{
	for (int i = 0; i < 8; i++)for (int j = 0; j < 7; j++)Read(x[i][j]);
	show();
	cv::waitKey(0);
	return 0;
}

3,U型

和大拇指类似,U型也可以用来生成解。

代码几乎是一样的,微改一下即可:

int main()
{
	freopen("D:/out.txt", "w", stdout);
	while (true)
	{
		for (int i = 0; i < 8; i++)for (int j = 0; j < 7; j++)Read(x[i][j]);
		for (int k = 1; k < 10; k++)
		{
			int xmin = 10, ymin = 10, xmax = 0, ymax = 0;
			for (int i = 0; i < 8; i++)for (int j = 0; j < 7; j++)
			{
				if (x[i][j] != k)continue;
				xmin = min(xmin, i);
				ymin = min(ymin, j);
				xmax = max(xmax, i);
				ymax = max(ymax, j);
			}
			if ((xmax - xmin + 1) * (ymax - ymin + 1) != 6)continue;
			bool flag = true;
			int n = 0;
			for (int i = xmin; i <= xmax && flag; i++)for (int j = ymin; j <= ymax && flag; j++)
			{
				if (x[i][j] == k)continue;
				if (x[i][j])flag = false;
				n++;
			}
			if (!flag || n > 1)continue;
			if (!(x[xmin][ymin] && x[xmin][ymax] && x[xmax][ymin] && x[xmax][ymax]))continue;
			//cout << "                                  "<<k << endl;
			int tmp = x[(xmin + xmax) / 2][(ymin + ymax) / 2];
			x[(xmin + xmax) / 2][(ymin + ymax) / 2] = x[xmin + 1][ymin + 1], x[xmin + 1][ymin + 1] = tmp;
			if (check()) {
				cout << m << "月" << d << "日周" << s[w] << endl;
				for (int i = 0; i < 8; i++) {
					for (int j = 0; j < 7; j++) {
						cout << setw(3) << x[i][j];
					}
					cout << endl;
				}
			}
			break;
		}
	}
	return 0;
}

4,新解

由于产生的新解太多,所以挪到本地磁盘了。

5,日期汇总

为了自动去重,建立日期汇总,每次有一个解产生时,都把日期加进来。和本地文件保持同步。

1月15日周二
1月8日周二

2月1日周二
2月1日周六
2月2日周三
2月2日周五
2月2日周二
2月3日周四
2月4日周三
2月4日周一
2月4日周六
2月4日周四
2月4日周五
2月5日周六
2月5日周三
2月5日周四
2月5日周一
2月6日周二
2月6日周日
2月7日周一
2月8日周二
2月9日周三
2月9日周五
2月9日周二
2月10日周日
2月10日周四
2月11日周六
2月11日周五
2月12日周六
2月13日周日
2月14日周日
2月14日周四
2月14日周一
2月15日周二
2月15日周五
2月16日周三
2月17日周二
2月17日周四
2月18日周五
2月19日周六
2月19日周日
2月20日周日
2月20日周四
2月21日周一
2月22日周二
2月23日周二
2月23日周三
2月24日周四
2月25日周五
2月26日周六
2月27日周日
2月28日周一
2月29日周四
2月28日周五
2月22日周五
2月28日周二

3月10日周四
3月11日周五
3月12日周六
3月13日周日
3月14日周一
3月15日周二
3月16日周三
3月17日周四
3月18日周六
3月19日周六
3月1日周二
3月20日周日
3月21日周一
3月22日周二
3月22日周四
3月23日周三
3月24日周四
3月25日周五
3月26日周六
3月26日周日
3月27日周日
3月28日周一
3月29日周二
3月29日周四
3月2日周二
3月2日周三
3月30日周三
3月31日周四
3月3日周四
3月4日周二
3月4日周五
3月5日周六
3月5日周日
3月6日周六
3月6日周日
3月7日周一
3月8日周二
3月9日周三

4月1日周二
4月1日周六
4月1日周五
4月2日周二
4月2日周六
4月2日周四
4月2日周五
4月3日周日
4月4日周六
4月4日周三
4月4日周四
4月4日周一
4月4日周五
4月5日周五
4月5日周二
4月5日周日
4月6日周三
4月7日周四
4月6日周二
4月9日周二
4月9日周五
4月8日周五
4月9日周六
4月10日周二
4月10日周日
4月11日周一
4月12日周二
4月11日周二
4月12日周五
4月12日周一
4月14日周一
4月14日周二
4月14日周四
4月15日周一
4月15日周二
4月15日周三
4月15日周四
4月15日周五
4月15日周六
4月16日周日
4月13日周三
4月12日周日
4月17日周四
4月13日周四
4月10日周四
4月17日周日
4月18日周一
4月16日周六
4月19日周二
4月17日周六
4月19日周日
4月20日周六
4月20日周三
4月20日周四
4月21日周四
4月21日周六
4月22日周五
4月23日周一
4月23日周二
4月24日周二
4月25日周六
4月26日周五
4月26日周二
4月28日周二
4月28日周六
4月28日周三
4月28日周四
4月28日周一
4月28日周五
4月29日周五
4月29日周二
4月29日周六
4月30日周六
4月23日周六
4月27日周六
4月19日周六
4月25日周二
4月13日周二
4月14日周三
4月27日周六
4月18日周六
4月20日周五
4月9日周三
4月24日周日
4月24日周三
4月25日周一
4月29日周四
4月30日周三
4月12日周四
4月24日周一
4月31日周四

6月6日周三
6月2日周二
6月12日周二
6月18日周二
6月30日周二
6月2日周五
6月12日周五
6月18日周五
6月30日周五

7月1日周五
7月1日周二
7月15日周二
7月8日周二
7月17日周二
7月17日周四
7月17日周日
7月25日周二

8月10日周四
8月11日周五
8月12日周六
8月13日周日
8月14日周一
8月16日周三
8月19日周六
8月1日周二
8月1日周六
8月20日周日
8月21日周一
8月23日周三
8月24日周四
8月26日周六
8月27日周日
8月28日周一
8月2日周三
8月3日周四
8月6日周日
8月7日周一
8月8日周二
8月9日周三
8月6日周二
8月17日周二
8月17日周四
8月17日周日
8月25日周二
8月22日周五
8月28日周二
8月14日周二
8月12日周五
8月12日周二
8月14日周五
8月26日周五
8月28日周五
8月26日周二
8月17日周五

9月3日周四
9月5日周六
9月8日周二
9月6日周六
9月6日周日
9月5日周日
9月6日周日
9月12日周六
9月13日周日
9月14日周一
9月15日周二
9月16日周三
9月17日周四
9月19日周六
9月20日周日
9月16日周五
9月16日周二
9月22日周四
9月21日周一
9月22日周二
9月24日周四
9月24日周一
9月25日周一
9月25日周六
9月25日周五
9月26日周六
9月26日周日
9月29日周四
9月27日周日
9月29日周二
9月30日周三

10月2日周六
10月4日周一
10月4日周六
10月4日周四
10月4日周三
10月4日周一
10月6日周三
10月7日周四
10月8日周五
10月9日周六
10月9日周二
10月9日周三
10月10日周日
10月10日周二
10月10日周四
10月12日周日
10月12日周四
10月12日周二
10月12日周六
10月13日周三
10月13日周一
10月13日周六
10月13日周四
10月13日周二
10月14日周二
10月14日周三
10月14日周六
10月15日周日
10月15日周六
10月15日周六
10月15日周三
10月16日周三
10月16日周日
10月16日周六
10月17日周六
10月17日周四
10月17日周日
10月18日周一
10月18日周五
10月18日周六
10月19日周二
10月19日周日
10月19日周六
10月20日周四
10月20日周六
10月20日周五
10月20日周三
10月21日周四
10月21日周三
10月21日周一
10月21日周五
10月21日周六
10月22日周五
10月22日周日
10月22日周三
10月23日周二
10月23日周六
10月24日周二
10月24日周日
10月24日周三
10月25日周六
10月25日周二
10月26日周二
10月27日周六
10月28日周二
10月29日周六
10月28日周四
10月28日周三
10月28日周一
10月28日周六
10月28日周四
10月29日周三
10月29日周四
10月29日周日
10月30日周三
10月30日周六
10月31日周日
10月31日周四
10月31日周三

11月1日周日
11月1日周二
11月1日周四
11月10日周二
11月10日周四
11月12日周二
11月14日周二
11月16日周二
11月17日周四
11月17日周二
11月19日周二
11月20日周日
11月26日周五
11月26日周六
11月28日周五
11月27日周六
11月25日周六

12月6日周三

6,完整代码

//#include "data.h"

#include <iostream>
#include <vector>
#include <iomanip>
#include <map>
#include <string>
using namespace std;

#define OUT(x) cout << endl << #x << " = "; Print(x);
template<typename T>
inline void Print(T x)
{
	cout << x << " ";
}
template<typename T>
inline void Read(T& x)
{
	while (!(cin >> x)) { // only cin type T, ignore other info
		cin.clear();
		cin.ignore();
	}
}

const float theNan = 0.123456; //float默认只有6位
void Print(float x)
{
	if (std::isnan(x)) cout << theNan << " ";
	else cout << x << " ";
}
void Read(float& x)
{
	Read<float>(x);
	if (x == theNan) x = NAN;
}

int x[8][7];
int m, d, w;
string s[] = { "日","一","二" ,"三" ,"四" ,"五" ,"六" };
bool valid(int i, int j)
{
	if (i < 2 && j == 6)return false;
	if (i == 7 && j < 4)return false;
	return true;
}
bool check()
{
	int k = 0;
	for (int i = 0; i < 8; i++)for (int j = 0; j < 7; j++) {
		if (!valid(i, j))continue;
		if (x[i][j] == 0) {
			if (k == 0) {
				if (i > 1)
					return false;
				m = i * 6 + j + 1; // 1-12
			}
			else if (k == 1) {
				if (i <= 1)
					return false;
				if (i * 7 + j >= 45)
					return false;
				d = (i - 2) * 7 + j + 1; // 1-31
			}
			else {
				if (i * 7 + j < 45)
					return false;
				w = (i - 6) * 3 + j - 3; // 0-6
			}
			k++;
		}
	}
}

int main()
{
	freopen("D:/p/date.txt", "r", stdin);
	string s1;
	map<string, int>sm;
	while (cin >> s1)sm[s1] = 1;
	freopen("CON", "r", stdin);
	while (true)
	{
		for (int i = 0; i < 8; i++)for (int j = 0; j < 7; j++)Read(x[i][j]);
		for (int k = 1; k < 10; k++)
		{
			int xmin = 10, ymin = 10, xmax = 0, ymax = 0;
			for (int i = 0; i < 8; i++)for (int j = 0; j < 7; j++)
			{
				if (x[i][j] != k)continue;
				xmin = min(xmin, i);
				ymin = min(ymin, j);
				xmax = max(xmax, i);
				ymax = max(ymax, j);
			}
			if ((xmax - xmin + 1) * (ymax - ymin + 1) != 6)continue;
			bool flag = true;
			int n = 0;
			for (int i = xmin; i <= xmax && flag; i++)for (int j = ymin; j <= ymax && flag; j++)
			{
				if (x[i][j] == k)continue;
				if (x[i][j])flag = false;
				n++;
			}
			if (!flag || n > 1)continue;
			if (!(x[xmin][ymin] && x[xmin][ymax] && x[xmax][ymin] && x[xmax][ymax])) {
				for (int i = 0; i < 4; i++) {
					int tmp = x[xmin][ymin];
					x[xmin][ymin] = x[xmin][ymax];
					x[xmin][ymax] = x[xmax][ymin];
					x[xmax][ymin] = x[xmax][ymax];
					x[xmax][ymax] = tmp;
					
					if (check()) {
						string ts = to_string(m) + "月" + to_string(d) + "日周" + s[w];
						if (sm[ts])continue;
						sm[ts] = 1;
						cout << ts << endl;
						for (int i = 0; i < 8; i++) {
							for (int j = 0; j < 7; j++) {
								cout << setw(3) << x[i][j];
							}
							cout << endl;
						}
					}
				}
			}
			else {
				int tmp = x[(xmin + xmax) / 2][(ymin + ymax) / 2];
				x[(xmin + xmax) / 2][(ymin + ymax) / 2] = x[xmin + 1][ymin + 1], x[xmin + 1][ymin + 1] = tmp;
				if (check()) {
					string ts = to_string(m) + "月" + to_string(d) + "日周" + s[w];
					if (sm[ts])continue;
					sm[ts] = 1;
					cout << ts << endl;
					for (int i = 0; i < 8; i++) {
						for (int j = 0; j < 7; j++) {
							cout << setw(3) << x[i][j];
						}
						cout << endl;
					}
				}
			}
		}
	}
	return 0;
}

七,全文说明

1,每天可以在本文搜索一下,如果已经有解了就不用再拼了。否则,新拼出一个解时,把照片放到本地,等每隔一段时间再统一数字化。

2,数字化时,通过批量命名把所有图片改成img*.jpg,然后运行第五章第9节的完整代码,把图片转化成数字。

3,把数字化的结果中的日期,更新到(本地)日期汇总中。

4,运行第六章第6节的完整代码,产生新解,把日期,更新到(本地)日期汇总中。

5,把以上两步产生的解,更新到第二章每日拼图,或者第六章新解。

PS:这些新解不用再试图用来产生其他新解了,不会再有了。

6,把本地日期汇总更新到第六章第5节。

7,如果想查看一个数字化的解,可以运行第六章第2节的代码。

PS:本文一共有三个完整代码,本章说明都已提到。

猜你喜欢

转载自blog.csdn.net/nameofcsdn/article/details/123765415