puzzle(0412)日历拼图
目录
一,规则
每天根据月、日、星期去掉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
2022年5月
5月1日周日
1 1 1 2 0 2 0
1 1 8 2 2 2 0
0 4 8 9 9 3 3
4 4 8 10 9 9 3
4 7 8 10 5 5 3
4 7 10 10 6 5 3
7 7 7 0 6 5 5
0 0 0 0 6 6 6
5月2日周一
1 1 2 3 0 3 0
1 1 2 3 3 3 0
1 0 2 2 2 6 6
4 4 5 6 6 6 8
9 4 5 5 5 5 8
9 4 4 10 10 7 8
9 9 10 10 0 7 8
0 0 0 0 7 7 7
5月3日周二
1 1 1 2 0 2 0
1 4 4 2 2 2 0
1 4 0 3 8 8 5
4 4 6 3 8 5 5
6 6 6 3 8 5 9
7 7 6 3 3 5 9
7 7 7 10 10 0 9
0 0 0 0 10 10 9
5月4日周三
8 8 8 8 0 1 0
2 2 3 3 3 1 0
2 4 3 0 3 1 1
2 4 4 4 10 9 1
2 6 5 4 10 9 9
6 6 5 10 10 7 9
6 6 5 5 5 7 0
0 0 0 0 7 7 7
5月5日周四
8 1 1 2 0 2 0
8 3 1 2 2 2 0
8 3 1 1 0 4 4
8 3 3 3 9 4 4
5 5 9 9 9 4 7
6 5 5 5 7 7 7
6 6 6 6 10 10 7
0 0 0 0 0 10 10
5月6日周五
1 1 1 2 0 2 0
1 3 3 2 2 2 0
1 4 3 3 3 0 5
8 4 4 4 5 5 5
8 9 9 4 6 6 5
8 10 9 9 6 7 7
8 10 10 10 6 7 7
0 0 0 0 6 0 7
5月7日周六
1 1 1 1 0 2 0
1 9 8 2 2 2 0
9 9 8 3 3 2 0
9 4 8 3 5 5 5
10 4 8 3 3 6 5
10 4 4 6 6 6 5
10 10 4 6 7 7 7
0 0 0 0 7 7 0
5月8日周日
1 1 1 1 0 2 0
1 3 3 3 5 2 0
4 4 3 5 5 2 2
0 4 3 5 5 8 2
6 4 4 8 8 8 10
6 9 9 9 9 10 10
6 6 6 0 7 10 7
0 0 0 0 7 7 7
5月9日周一
1 2 2 3 0 3 0
1 1 2 3 3 3 0
1 1 2 2 5 5 5
8 0 4 5 5 10 9
8 8 4 10 10 10 9
6 8 4 4 4 7 9
6 6 6 6 0 7 9
0 0 0 0 7 7 7
5月10日周二
1 1 1 2 0 2 0
1 3 3 2 2 2 0
1 3 8 8 8 8 4
3 3 0 5 4 4 4
5 5 5 5 10 9 4
6 6 10 10 10 9 9
6 6 6 7 7 0 9
0 0 0 0 7 7 7
5月11日周三
1 1 1 2 0 2 0
1 1 3 2 2 2 0
8 8 3 3 3 4 4
5 8 8 0 3 6 4
5 9 9 9 6 6 4
5 5 5 9 6 7 4
10 10 10 10 6 7 0
0 0 0 0 7 7 7
5月12日周四
1 1 2 3 0 3 0
1 1 2 3 3 3 0
8 1 2 2 4 4 4
8 8 8 2 0 4 9
5 5 5 10 10 4 9
5 6 10 10 7 7 9
5 6 6 6 6 7 9
0 0 0 0 0 7 7
5月13日周五
8 8 8 8 0 1 0
9 9 2 2 2 1 0
10 9 9 2 3 1 1
10 10 10 2 3 0 1
4 4 6 6 3 3 3
4 4 6 5 5 5 5
4 6 6 5 7 7 7
0 0 0 0 7 0 7
5月14日周六
1 1 1 2 0 2 0
1 3 3 2 2 2 0
1 3 3 8 8 4 4
9 3 10 10 8 4 0
9 10 10 5 8 4 7
9 5 5 5 6 4 7
9 5 6 6 6 7 7
0 0 0 0 6 7 0
5月15日周日
8 8 8 1 0 1 0
2 9 8 1 1 1 0
2 9 9 10 10 10 10
2 2 9 3 3 3 3
0 2 4 3 5 5 5
6 6 4 4 4 7 5
6 6 6 0 4 7 5
0 0 0 0 7 7 7
5月16日周一
1 1 1 2 0 2 0
1 3 3 2 2 2 0
1 8 3 4 4 5 5
8 8 3 4 4 5 9
8 0 3 4 5 5 9
10 10 10 6 6 7 9
10 6 6 6 0 7 9
0 0 0 0 7 7 7
5月17日周二
1 1 1 2 0 2 0
1 8 8 2 2 2 0
1 3 8 8 9 9 9
10 3 3 3 3 5 9
10 4 0 5 5 5 6
10 4 4 5 6 6 6
10 4 4 7 7 0 6
0 0 0 0 7 7 7
5月18日周三
1 1 1 2 0 2 0
8 8 1 2 2 2 0
9 8 1 3 3 4 4
9 8 10 3 3 3 4
9 10 10 0 6 6 4
9 10 5 5 6 7 4
5 5 5 6 6 7 0
0 0 0 0 7 7 7
5月19日周四
1 1 1 2 0 2 0
8 1 4 2 2 2 0
8 1 4 3 3 3 3
8 4 4 3 5 5 5
8 4 10 10 0 5 5
9 10 10 6 7 7 7
9 9 9 6 6 6 7
0 0 0 0 0 6 7
5月20日周五
8 1 1 1 0 2 0
8 1 4 4 2 2 0
8 1 4 5 2 3 3
8 4 4 5 2 3 3
9 9 5 5 5 0 3
10 9 9 6 6 6 6
10 10 10 6 7 7 7
0 0 0 0 7 0 7
5月21日周六
1 1 1 2 0 6 0
1 2 2 2 3 6 0
1 2 3 3 3 6 6
7 7 7 4 3 4 6
8 8 7 4 4 4 0
10 8 8 9 9 9 9
10 10 10 10 5 5 5
0 0 0 0 5 5 0
5月22日周日
8 1 9 9 0 2 0
8 1 10 9 9 2 0
8 1 10 10 10 2 2
8 1 1 3 3 3 2
5 5 4 3 3 6 6
0 5 4 4 4 6 7
5 5 4 0 6 6 7
0 0 0 0 7 7 7
5月23日周一
8 1 1 2 0 2 0
8 8 1 2 2 2 0
3 8 1 1 4 4 4
3 10 10 10 4 4 9
3 3 3 10 6 6 9
5 0 6 6 6 7 9
5 5 5 5 0 7 9
0 0 0 0 7 7 7
5月24日周二
8 1 1 2 0 2 0
8 3 1 2 2 2 0
8 3 1 1 9 9 10
8 3 3 3 9 10 10
4 4 4 4 9 10 6
5 5 0 4 6 6 6
5 5 5 7 7 0 6
0 0 0 0 7 7 7
5月25日周三
1 1 1 2 0 2 0
1 1 3 2 2 2 0
8 8 3 3 3 4 4
5 8 8 9 3 6 4
5 9 9 9 6 6 4
5 5 5 0 6 7 4
10 10 10 10 6 7 0
0 0 0 0 7 7 7
5月26日周四
8 1 1 2 0 2 0
8 8 1 2 2 2 0
3 8 1 1 5 4 4
3 9 9 9 5 4 4
3 9 5 5 5 4 10
3 3 6 6 0 7 10
6 6 6 7 7 7 10
0 0 0 0 0 7 10
5月27日周五
1 1 1 2 0 5 0
1 2 2 2 3 5 0
1 2 4 3 3 5 8
4 4 4 3 5 5 8
9 9 4 3 10 10 8
9 6 6 10 10 0 8
9 6 6 6 7 7 7
0 0 0 0 7 0 7
5月28日周六
1 1 1 2 0 2 0
1 3 3 2 2 2 0
1 9 3 8 8 8 8
9 9 3 6 10 10 10
9 5 3 6 4 4 10
5 5 6 6 6 4 0
5 5 7 7 7 4 4
0 0 0 0 7 7 0
5月29日周日
1 1 1 2 0 2 0
3 1 4 2 2 2 0
3 1 4 4 5 5 5
3 3 4 4 5 6 6
8 3 9 9 5 6 10
8 8 9 7 7 6 10
0 8 9 0 7 6 10
0 0 0 0 7 7 10
5月30日周一
8 1 1 2 0 2 0
8 3 1 2 2 2 0
8 3 1 1 9 9 9
8 3 3 3 5 10 9
4 5 5 5 5 10 10
4 4 4 6 6 6 10
4 0 6 6 0 7 7
0 0 0 0 7 7 7
5月31日周二
1 1 1 2 0 2 0
1 1 3 2 2 2 0
4 4 3 3 3 8 8
7 4 3 8 8 8 5
7 4 4 5 5 5 5
7 9 9 9 10 10 6
7 9 0 10 10 0 6
0 0 0 0 6 6 6
2022年6月
6月1日周三
1 2 2 3 3 0 0
1 1 2 3 3 3 0
0 1 2 2 4 4 4
8 1 5 5 5 5 4
8 9 9 10 10 5 4
8 9 10 10 7 6 6
8 9 7 7 7 6 0
0 0 0 0 7 6 6
6月2日周四
1 1 2 2 2 0 0
1 1 4 3 2 2 0
1 0 4 3 3 3 5
4 4 4 3 5 5 5
6 6 8 8 5 9 7
6 8 8 9 9 9 7
6 6 10 10 10 10 7
0 0 0 0 0 7 7
6月3日周五
1 2 3 3 3 0 0
1 2 2 2 3 3 0
1 2 0 7 7 9 8
1 1 5 7 9 9 8
4 5 5 7 9 6 8
4 5 5 6 6 6 8
4 4 4 6 10 10 10
0 0 0 0 10 0 10
6月4日周六
8 1 2 2 2 0 0
8 1 2 4 4 3 0
8 1 2 0 4 3 3
8 1 1 4 4 5 3
9 9 10 5 5 5 3
9 10 10 5 7 6 6
9 10 7 7 7 6 6
0 0 0 0 7 6 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,日期汇总
为了自动去重,建立日期汇总,每次有一个解产生时,都把日期加进来。以本地文件为准。
6,完整代码V1
//#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;
}
7,BUG修复
如果同时存在大拇指和U型新解,那么上面的代码只能产生一部分新解。
由于这个代码已经运行一个月,目前一共已经得到400种不同的解法,所以解决方法最好还要能修复之前缺失的新解。
所以我把控制台输入改成从文件输入,把历史所有解当做输入,清算有没有漏的。
再修复一个BUG,之前的代码遗漏了数字为10的块也可以以解生解。
代码略,参见下一节。
8,日期优化(完整代码V2)
记录日期的文件除了被程序读取使用外,还被我肉眼读,因为我经常手动以解生解,需要查看哪些日期还没有解。
日期逐渐多了之后,不太好看了,所以我优化了数据结构,以“-1月1日”表示1月1日的从周一到周日的七个组合都有解了。
代码只需要把读取日期记录文件的地方略改即可。
完整代码:
#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++;
}
}
if (m == 2 && d > 29)return false;
if ((m == 4 || m == 6 || m == 9 || m == 11) && d > 30)return false;
return true;
}
void go()
{
freopen("D:/p/date.txt", "r", stdin);
string s1;
map<string, int>sm;
while (cin >> s1) {
sm[s1] = 1;
if (s1[0] == '-') {
s1 = s1.substr(1, s1.length() - 1);
for (int i = 0; i < 7; i++)sm[s1 + "周" + s[i]] = 1;
}
}
freopen("D:/p/ans.txt", "r", stdin);
while (true)
{
int a, b;
Read(a); Read(b);
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;
}
}
}
}
}
}
int main()
{
go();
return 0;
}
9,BUG修复、新增检查(完整代码V3)
经过了前面的BUG修复之后,仍然存在以解生解不全的问题。
所以我又把2种以解生解的模式做了拆分,先做U型再做大拇指,分开成2次编译运行,先只调用go后只调用go1,这样才能确保生成所有的解。
然后新增了go2和go3,需要检查的时候调用即可。
完整代码:
#include<iostream>
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;
}
template<typename T1, typename T2>
inline void Print(const std::pair<T1, T2>& p)
{
Print(p.first);
Print(p.second);
}
template<typename T1, typename T2>
inline void Print(const map<T1, T2>& aMap)
{
cout << " size = " << aMap.size() << endl;
for (auto& it : aMap) {
Print(it);
cout << endl;
}
}
template<typename T>
inline void Print(const vector<T>& vec)
{
cout << " size = " << vec.size() << endl;
for (auto& it : vec) {
Print(it);
}
}
template<typename T1, typename T2>
inline void Read(std::pair<T1, T2>& p)
{
Read(p.first);
Read(p.second);
}
template<typename Tkey, typename Tvalue>
void Read(std::map<Tkey, Tvalue>& aMap)
{
int num;
Read(num);
std::pair<Tkey, Tvalue> p;
while (num--) {
Read(p);
aMap[p.first] = p.second;
}
}
template<typename T>
inline void Read(vector<T>& vec)
{
int num;
Read(num);
vec.resize(num); // 慎用
for (int i = 0; i < num; i++) {
Read(vec[i]);
}
}
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++;
}
}
if (m == 2 && d > 29)return false;
if ((m == 4 || m == 6 || m == 9 || m == 11) && d > 30)return false;
return true;
}
void go() //以解生解
{
freopen("D:/p/date.txt", "r", stdin);
string s1;
map<string, int>sm;
while (cin >> s1) {
sm[s1] = 1;
if (s1[0] == '-') {
s1 = s1.substr(1, s1.length() - 1);
for (int i = 0; i < 7; i++)sm[s1 + "周" + s[i]] = 1;
}
}
freopen("D:/p/ans.txt", "r", stdin);
while (true)
{
int a, b;
Read(a); Read(b);
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]) // U型
{
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;
}
}
}
}
}
}
void go1() //以解生解
{
freopen("D:/p/date.txt", "r", stdin);
string s1;
map<string, int>sm;
while (cin >> s1) {
sm[s1] = 1;
if (s1[0] == '-') {
s1 = s1.substr(1, s1.length() - 1);
for (int i = 0; i < 7; i++)sm[s1 + "周" + s[i]] = 1;
}
}
freopen("D:/p/ans.txt", "r", stdin);
while (true)
{
int a, b;
Read(a); Read(b);
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;
}
}
}
}
}
}
}
void go2()//检查日期记录有没有漏的
{
string s1;
map<string, int>sm;
freopen("D:/p/date.txt", "r", stdin);
while (cin >> s1) {
sm[s1] = 1;
if (s1[0] == '-') {
s1 = s1.substr(1, s1.length() - 1);
for (int i = 0; i < 7; i++)sm[s1 + "周" + s[i]] = 1;
}
}
cin.clear();
freopen("CON", "r", stdin);
freopen("D/p/out.txt", "w", stdout);
while (cin >> s1)
{
for (int i = 0; i < 8; i++)for (int j = 0; j < 7; j++)Read(x[i][j]);
if (sm[s1] == 0)cout << s1 << endl;
}
}
void go3()//检查有没有重复日期的解
{
string s1;
map<string, int>sm;
freopen("D/p/out.txt", "w", stdout);
while (cin >> s1)
{
for (int i = 0; i < 8; i++)for (int j = 0; j < 7; j++)Read(x[i][j]);
if (sm[s1] == 1)cout << s1 << endl;
sm[s1] = 1;
}
}
int main()
{
//go();
go1();
return 0;
}
七,说明
1,每天可以在本文搜索一下,如果已经有解了就不用再拼了。否则,新拼出一个解时,把照片放到本地,等每隔一段时间再统一数字化。
2,数字化时,通过批量命名把所有图片改成img*.jpg,然后运行第五章第9节的完整代码,把图片转化成数字。
3,把数字化的结果更新到(本地)结果汇总中,把其中的日期,更新到(本地)日期汇总中。
4,运行第六章第7节的完整代码V2,产生新解,跳到第3步,循环,直到再也没有新解。
5,如果想查看一个数字化的解,可以运行上面第六章第2节的代码,也可以运行下面的可视化V2版。
八,直接求解
1,可视化V2版
对显示答案做了微调,把月日周对应的3个格子用真实的内容显示出来。
int x[8][7];
void show()
{
int pix = 50;
Mat img = imread("D:/base.jpg",0);
resize(img, img, Size(pix * 7, pix * 8), 0, 0);
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 7; j++) {
if(x[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 * x[i][j];
}
}
}
cout << endl;
}
static int kid = 0;
imshow("ans" + to_string(kid++), img);
}
其中D:/base.jpg就是没有块的图:
运行效果:
2,直接求解
利用拼接覆盖问题通用求解代码,直接把12*31*7的所有组合全部求出来。
int r,c,blockNum; //自定义行列数,块数
map<Grid, int> ng,mg; //ng是自定义挖掉的格子,mg是有效格子
vector<Block>blocks;//自定义每个块的所有形态在最小位置包含的格子
vector<Grid> rotate(vector<Grid>& g)
{
int maxRow = 0, t;
for (auto& gi : g)maxRow = max(maxRow, gi.r);
for (auto& gi : g)t = gi.c, gi.c = maxRow - gi.r, gi.r = t;
return g;
}
vector<Grid> reverse(vector<Grid> &g)
{
for (auto& gi : g)gi.r ^= gi.c ^= gi.r ^= gi.c;
return g;
}
void init1(int m,int d,int w)
{
r = 8, c = 7, blockNum = 10;
ng.clear(), mg.clear();
ng[Grid{ 0,6 }] = ng[Grid{ 1,6 }] = ng[Grid{ 7,0 }] = ng[Grid{ 7,1 }] = ng[Grid{ 7,2 }] = ng[Grid{ 7,3 }] = 1;
m--, d--, w %= 7;
ng[Grid{ m / 6,m % 6 }] = ng[Grid{ d / 7 + 2,d % 7 }] = 1;
ng[Grid{ w / 4 + 6,w % 4 + 3 + (w > 3) }] = 1;
}
void init2()
{
vector<Grid>v = { {0,0},{0,1},{1,1},{1,2},{1,3} };
blocks[0] = Block{ { v,rotate(v),rotate(v),rotate(v), reverse(v),rotate(v),rotate(v),rotate(v)}, r, c, mg };
v = { {0,0},{0,1},{0,2},{1,0},{1,1} };
blocks[1] = Block{ { v,rotate(v),rotate(v),rotate(v), reverse(v),rotate(v),rotate(v),rotate(v)}, r, c, mg };
v = { {0,0},{0,1},{0,2},{1,0} };
blocks[2] = Block{ { v,rotate(v),rotate(v),rotate(v), reverse(v),rotate(v),rotate(v),rotate(v)}, r, c, mg };
v = { {0,0},{0,1},{0,2},{0,3}, {1,0} };
blocks[3] = Block{ { v,rotate(v),rotate(v),rotate(v), reverse(v),rotate(v),rotate(v),rotate(v)}, r, c, mg };
v = { {0,0},{0,1},{0,2},{1,0},{2,0} };
blocks[4] = Block{ {v,rotate(v),rotate(v),rotate(v)},r,c, mg };
v = { {0,0},{0,1},{0,2},{1,1},{2,1} };
blocks[5] = Block{ {v,rotate(v),rotate(v),rotate(v)},r,c, mg };
v = { {0,0},{0,1},{0,2},{0,3} };
blocks[6] = Block{ {v,rotate(v)},r,c, mg };
v = { {0,0},{0,1},{1,1},{1,2} };
blocks[7] = Block{ { v,rotate(v), reverse(v),rotate(v)}, r, c, mg };
v = { {0,0},{0,1},{0,2},{1,0} ,{1,2} };
blocks[8] = Block{ {v,rotate(v),rotate(v),rotate(v)},r,c, mg };
v = { {0,0},{0,1},{1,1},{2,1},{2,2} };
blocks[9] = Block{ {v,rotate(v),reverse(v),rotate(v)},r,c, mg };
}
void solve(int m, int d, int w)
{
init1(m, d, w);
int id = 0;
for (int i = 0; i < r; i++)for (int j = 0; j < c; j++) {
if (ng[Grid{ i, j }] == 0)mg[Grid{ i, j }] = ++id;
}
blocks.resize(blockNum);
init2();
vector<vector<Grid>> grids = Cover(blocks, mg);
vector<vector<int>>v(r);
for (int i = 0; i < r; i++)v[i].resize(c);
for (int i = 0; i < grids.size(); i++) {
for (auto& g : grids[i])v[g.r][g.c] = i + 1;
}
for (int i = 0; i < r; i++) {
for (int j = 0; j < v[i].size(); j++)cout << v[i][j] << " ";
cout << endl;
}
}
int main()
{
ios::sync_with_stdio(false);
clock_t start, endd;
start = clock();
freopen("D:ans.txt", "w", stdout);
for (int m = 1; m <= 12; m++)for (int d = 1; d <= 31; d++)for (int w = 1; w <= 7; w++) {
solve(m, d, w);
cout << endl;
}
endd = clock();
double endtime = (double)(endd - start) / CLOCKS_PER_SEC;
cout << "Total time:" << endtime << endl; //s为单位
return 0;
}
输出:
0 3 3 3 9 9 0
2 2 3 8 9 9 0
0 2 3 8 8 9 10
5 2 2 7 8 10 10
5 7 7 7 8 10 4
5 5 5 6 1 1 4
6 6 6 6 0 1 4
0 0 0 0 1 1 4
0 3 3 4 4 4 0
2 2 3 3 4 6 0
0 2 5 5 4 6 7
8 2 2 5 10 6 7
8 8 9 5 10 6 7
8 8 9 10 10 7 7
9 9 9 10 1 0 1
0 0 0 0 1 1 1
0 5 5 5 6 6 0
4 4 5 9 10 6 0
0 4 5 9 10 6 3
4 4 8 9 10 10 3
7 8 8 9 9 10 3
7 8 8 2 1 1 3
7 7 7 2 2 1 0
0 0 0 0 2 1 1
0 2 2 3 3 3 0
1 1 2 2 3 8 0
0 1 5 5 3 8 8
4 1 1 5 10 8 8
4 6 5 5 10 10 7
4 6 9 9 9 10 7
4 6 6 6 9 10 7
0 0 0 0 0 7 7
0 3 3 4 4 4 0
2 2 3 3 4 6 0
0 2 5 5 4 6 8
7 2 2 5 10 6 8
7 7 9 5 10 6 8
7 7 9 10 10 8 8
9 9 9 10 1 1 1
0 0 0 0 1 0 1
0 2 2 3 3 3 0
1 1 2 2 3 8 0
0 1 5 5 3 8 10
4 1 1 5 8 8 10
4 6 5 5 9 9 10
4 6 9 9 9 10 10
4 6 6 6 7 7 7
0 0 0 0 7 7 0
0 2 2 3 3 3 0
1 1 2 2 3 9 0
0 1 6 6 3 9 9
4 1 1 6 7 9 9
4 5 5 6 7 10 8
4 5 7 7 7 10 8
4 5 5 0 10 10 8
0 0 0 0 10 8 8
0 2 2 10 10 10 0
2 2 8 8 8 10 0
3 0 8 9 9 10 7
3 9 9 9 7 7 7
3 5 6 6 7 4 4
3 5 6 6 1 1 4
5 5 5 6 0 1 4
0 0 0 0 1 1 4
0 2 2 5 5 5 0
2 2 5 5 10 7 0
3 0 10 10 10 7 9
3 3 3 8 10 7 9
4 4 3 8 7 7 9
4 6 6 8 8 8 9
4 6 6 6 1 0 1
0 0 0 0 1 1 1
0 4 4 4 6 6 0
5 5 4 9 10 6 0
5 0 4 9 10 6 3
5 5 8 9 10 10 3
7 8 8 9 9 10 3
7 8 8 2 1 1 3
7 7 7 2 2 1 0
0 0 0 0 2 1 1
0 3 3 4 4 6 0
2 2 3 3 4 6 0
2 0 9 8 4 6 6
2 2 9 8 8 8 6
10 10 9 8 5 5 5
10 10 9 9 1 1 5
10 7 7 7 7 1 5
0 0 0 0 0 1 1
0 2 2 5 5 5 0
2 2 5 5 10 7 0
3 0 10 10 10 7 9
3 3 3 8 10 7 9
4 4 3 8 7 7 9
4 6 6 8 8 8 9
4 6 6 6 1 1 1
0 0 0 0 1 0 1
0 3 3 3 3 8 0
1 1 8 8 8 8 0
1 0 10 10 9 9 9
1 1 4 10 10 10 9
4 4 4 6 6 6 9
4 2 2 6 5 7 7
2 2 5 5 5 7 7
0 0 0 0 5 7 0
0 3 3 5 5 5 0
1 1 3 5 9 10 0
1 0 3 3 9 10 10
1 1 4 6 9 10 10
2 4 4 6 9 7 8
2 4 6 6 6 7 8
2 2 2 0 7 7 8
0 0 0 0 7 8 8
......省略2千多个解
4 2 2 2 1 1 0
4 2 5 2 1 0 0
4 4 5 1 1 6 3
9 4 5 5 5 6 3
9 9 9 8 8 6 3
9 10 10 8 6 6 3
10 10 0 8 7 7 7
0 0 0 0 7 7 0
4 2 2 2 1 1 0
4 2 7 2 1 0 0
4 7 7 1 1 5 3
4 7 5 5 5 5 3
10 7 9 9 3 3 3
10 9 9 8 8 8 6
10 10 0 0 8 6 6
0 0 0 0 8 6 6
Total time:12.741