题目
6x6的方格,沿着格子的边线剪开成两部分。
要求这两部分的形状完全相同。
如图:p1.png, p2.png, p3.png 就是可行的分割法。
试计算:
包括这3种分法在内,一共有多少种不同的分割方法。
注意:旋转对称的属于同一种分割法。
答案:509
方法一:dfs
首先我们将边编号为0,1,2,3,4,5,6
然后从正中心开始分割
每次都是分割一条边时,与其对称边也要分割,如图:
每条边有四个方向可以走,dfs中只需用一个标记数组将走过的边及其对称边标记,同时枚举四个方向,dfs一遍后要将标记的边都恢复为0,当到达边界时,ans++并且return,最后算出来后别忘了除以4避免重复
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 6;
ll ans;
bool mp[N+1][N+1];
int dir[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};
void dfs(int x,int y)
{
if( x == 0 || y == 0 || x == N || y == N)/*如果到达边界证明裁剪完成*/
{
ans++;
return;
}
for(int i = 0; i < 4 ; i++)
{
int tx = x + dir[i][0];
int ty = y + dir[i][1];
if(mp[tx][ty]) continue;/*如果已经裁剪过*/
mp[tx][ty] = true;/*如果还未被裁剪 则裁剪次边并标记*/
mp[N - tx][N - ty] = true;/*同时还要裁剪起对称边*/
dfs(tx,ty);
mp[tx][ty] = false;
mp[N - tx][N - ty] = false;
}
}
int main()
{
mp[N / 2][N / 2] = 1;
dfs(N / 2,N / 2);
cout << ans / 4 << endl;/*由于旋转对称都是相同的所以正确结果应该是ans除以4*/
return 0;
}
方法二:排列组合
与dfs的思想差不多,不过这道题应用了排列组合的思想,这道题的目的是一个36个网格的图分为两个旋转对称的图,即一个图有18个网格,就是在36的网格个里面找两个平分连通图的个数,那么我们只需要找到一个裁剪为18个网格的图后判断其连通性即可,同时要注意,这道题要两个部分一起判断,比如当你剪裁了一半的时候要判断是否有两个连通的图,详见代码
ps:虽然这道题写出来比dfs要慢一些,但这种排列组合以及连通性的思想是值得学习的。
#include <iostream>
#include <cstring>
using namespace std;
const int n = 6;
int visited[n][n];/*0表示没有被剪裁,1表示裁剪 2表示裁剪的对立面*/
int ans;
void mark(int a[][n],int i,int j)/*将与a[i][j]连通的格子都标记为0*/
{
int tag = a[i][j];
a[i][j] = 0;
if(i - 1 >= 0 && a[i - 1][j] == tag) mark(a,i - 1,j);/*上*/
if(i + 1 < n && a[i + 1][j] == tag) mark(a,i + 1, j);/*下*/
if(j - 1 >= 0 && a[i][j - 1] == tag) mark(a,i,j - 1);/*左*/
if(j + 1 < n && a[i][j + 1] == tag) mark(a,i,j + 1);/*右*/
}
bool check(int a[][n])
{
int link_num = 0;/*连通区域的数量*/
for(int i = 0 ; i < n ; i++)
{
for(int j = 0; j < n ; j++)
{
if( a[i][j] > 0 )/*证明有连通区域*/
{
link_num++;
mark(a,i,j);/*将此点的连通区域都标记为0*/
}
}
}
return (link_num == 2);/*如果连通区域有两个*/
}
void dfs(int s,int k)/*s表示裁剪的起点,k表示已经裁剪的个数*/
{
if(k >= n * n / 2)/*如果剪裁了18个格子*/
{
int temp[n][n];
memcpy(temp,visited,sizeof(visited));
if(check(temp))/*如果有两个连通区域的*/
{
ans++;
}
return;
}
/*将二维的方格变成一维方便进行排列组合*/
for(int p = s; p < n * n ; p++)
{
int i = p / n, j = p % n;
if(!visited[i][j] && !visited[n - 1 - i][n - 1 - j])
{/*将裁剪的格子与其对立面一起裁剪*/
visited[i][j] = 1,visited[n - 1 - i][n - 1 - j] = 2;
dfs(p + 1,k + 1);
visited[i][j] = 0, visited[n - 1 - i][n - 1 - j] = 0;
}
}
}
int main()
{
dfs(0,0);
cout << ans / 4 << endl;/*不要忘了除以4*/
}