10.04 T2 搜索+乱搞剪枝
Description
![](https://oj.bashu.com.cn/images/2651-1.png)
Input
![](https://oj.bashu.com.cn/images/2651-2.png)
Output
输出T行,每一行一个YES或NO,表示是否能够使图形中心对称。
Sample Input
6 1 2 3 ABC BAC
Sample Output
YES
Hint
【数据范围及约定】
![](https://oj.bashu.com.cn/images/2651-3.png)
![](https://oj.bashu.com.cn/images/2651-3.png)
法1:我的迭代加深,实测70分
code:
1 #include<iostream> 2 #include<cstdio> 3 #include<string> 4 using namespace std; 5 int m,n; 6 char g[20][20]; 7 void hang(int a,int b){ 8 for(int i=1;i<=m;i++)swap(g[a][i],g[b][i]); 9 } 10 void lie(int a,int b){ 11 for(int i=1;i<=n;i++)swap(g[i][a],g[i][b]); 12 } 13 void prt(){ 14 for(int i=1;i<=n;i++){ 15 for(int j=1;j<=m;j++){ 16 cout<<g[i][j]<<" "; 17 } 18 cout<<'\n'; 19 } 20 } 21 bool dfs(int dep,int limit){ 22 // cout<<"dep->"<<dep<<" limit->"<<limit<<'\n'<<endl; 23 if(dep==limit){ 24 for(int i=1;i<=n;i++){ 25 for(int j=1;j<=m;j++){ 26 if(g[i][j]!=g[n-i+1][m-j+1])return false; 27 } 28 } 29 // prt(); 30 return true; 31 } 32 for(int i=1;i<=n;i++){ 33 for(int j=i+1;j<=n;j++){ 34 hang(i,j); 35 if(dfs(dep+1,limit))return true; 36 hang(i,j); 37 } 38 } 39 for(int i=1;i<=m;i++){ 40 for(int j=i+1;j<=m;j++){ 41 lie(i,j); 42 if(dfs(dep+1,limit))return true; 43 lie(i,j); 44 } 45 } 46 return false; 47 } 48 int main(){ 49 freopen("fragment.in","r",stdin); 50 freopen("fragment.out","w",stdout); 51 int num,T; 52 cin>>num>>T; 53 while(T--){ 54 cin>>n>>m; 55 string temp; 56 for(int i=1;i<=n;i++){ 57 cin>>temp; 58 for(int j=1;j<=m;j++){ 59 g[i][j]=temp[j-1]; 60 } 61 } 62 int d=0; 63 while(1){ 64 if(d>=5){ 65 cout<<"NO"<<endl; 66 break; 67 } 68 if(dfs(0,d)==true){ 69 cout<<"YES"<<'\n'; 70 break; 71 } 72 d++; 73 // cout<<d<<'\n'; 74 } 75 } 76 return 0; 77 }
有一个很显然的性质,每一行每一列的元素组成都是不变的
所以我们先暴力每一行的情况,利用这一性质剪枝,也就是每一行与其相对的行的元素组成
是相同的,然后判断列是否满足。
有如下观察:
1、同一行/列的元素(可重)集合不变。
2、允许任意交换意味着我们可以随意排列行、列的顺序。
3、对称的两行/列元素(可重)集合相同。
4、对于已有的一个可行解,若其两组对称的行/列为 A和 A′、B 和 B′那么我们交换
A 和 B,A′和 B′后,它依然是一组合法解。
考虑枚举。若 N 为奇数,那么我们就先枚举中间的一行/列对应原矩阵的哪一行/列,这样,
剩余的行/列数变成了偶数。
考虑一次枚举对称的两行/列分别对应原矩阵的哪一行/列,由观察 4,我们不妨任取一个没
有被选取过的行/列,再枚举一个与之对称的行/列。
对于单次对行/列的排列的枚举,至多有 M=1!=11*9*7*5*3*1=10395 种可能的情况。
考虑预处理出每两行/列元素(可重)集合是否相同,并利用观察 3 进行剪枝。
直接枚举行和列的最终排列,并暴力判断方案的合法性,时间复杂度 O(H*W*M^2)。
但由于利用观察 3 进行的剪枝,无解的情况 M 都只会在在很小的范围内,而有解的情况在M 较大时往往情况简单(例如所有元素均相同),很容易就能够找到一组解,并结束搜索,因此,该算法的实际运行效率甚至比正解更好(最慢 2ms),以下代码实现了该算法。
正解的做法是先枚举行的最终排列,然后将列两两匹配,若能够剩余不足两列(一列或零列),
那么就找到了一组解。
时间复杂度 O(H*W^2*M)或 O(H*WLogW*M)。
code:
1 #include<iostream> 2 #include<cstdio> 3 #include<string> 4 #include<cstring> 5 #define mem(i) memset(i,0,sizeof i) 6 using namespace std; 7 int n,m; 8 int g[15][15],origin_hang[15],origin_lie[15],flag_of_solution,jihehang[15][27],jihelie[15][27]; 9 int used_lie[15],used_hang[15],equal_lie[15][15],equal_hang[15][15]; 10 void inital_array(){ 11 mem(origin_hang),mem(origin_lie),flag_of_solution=0,mem(jihehang),mem(jihelie),mem(used_lie); 12 mem(used_hang),mem(equal_lie),mem(equal_hang); 13 } 14 bool check_suc(){ 15 for(int i=1;i<=n;i++){ 16 for(int j=1;j<=m;j++){ 17 if(g[origin_hang[i]][origin_lie[j]]!=g[origin_hang[n-i+1]][origin_lie[m-j+1]])return false; 18 } 19 } 20 flag_of_solution=1; 21 return true; 22 } 23 bool check_equal_hang(int a,int b){ 24 for(int i=1;i<=26;i++){ 25 if(jihehang[a][i]!=jihehang[b][i])return false; 26 } 27 return true; 28 } 29 bool check_equal_lie(int a,int b){ 30 for(int i=1;i<=26;i++){ 31 if(jihelie[a][i]!=jihelie[b][i])return false; 32 } 33 return true; 34 } 35 void dfsY(int now,int flag_odd){ 36 if(flag_odd&&now==m/2+1){ 37 for(int i=1;i<=m;i++){ 38 origin_lie[now]=i; 39 used_lie[i]=1; 40 dfsY(now-1,0); 41 used_lie[i]=0; 42 origin_lie[now]=0; 43 if(flag_of_solution)return; 44 } 45 return; 46 } 47 if(now==0){ 48 check_suc(); 49 return; 50 } 51 for(int i=1;i<=m;i++){ 52 if(used_lie[i])continue; 53 origin_lie[now]=i; 54 used_lie[i]=1; 55 for(int j=1;j<=m;j++){ 56 if(i==j)continue; 57 if(used_lie[j])continue; 58 if(equal_lie[i][j]){ 59 origin_lie[m+1-now]=j; 60 used_lie[j]=1; 61 dfsY(now-1,0); 62 used_lie[j]=0; 63 origin_lie[m+1-now]=0; 64 if(flag_of_solution)return; 65 } 66 } 67 origin_lie[now]=0; 68 used_lie[i]=0; 69 } 70 } 71 void dfsX(int now,int flag_odd){ 72 if(flag_odd&&now==n/2+1){//如果行数是奇数,并且现在在讨论中间的情况 73 for(int i=1;i<=n;i++){ 74 origin_hang[now]=i; 75 used_hang[i]=1; 76 dfsX(now-1,0); 77 used_hang[i]=0; 78 origin_hang[now]=0; 79 if(flag_of_solution)return; 80 } 81 return; 82 } 83 if(now==0){ 84 dfsY((m+1)/2,m&1); 85 return; 86 } 87 for(int i=1;i<=n;i++){//现在不是中间,我们要找一个还没有使用过的集合来填进去 88 if(used_hang[i])continue;//如果这个行已经使用过了,continue 89 origin_hang[now]=i; 90 used_hang[i]=1; 91 for(int j=1;j<=n;j++){ 92 if(i==j)continue; 93 if(used_hang[j])continue; 94 if(equal_hang[i][j]){ 95 origin_hang[n+1-now]=j; 96 used_hang[j]=1; 97 dfsX(now-1,0); 98 used_hang[j]=0; 99 origin_hang[n+1-now]=j; 100 if(flag_of_solution)return; 101 } 102 } 103 origin_hang[now]=0; 104 used_hang[i]=0; 105 } 106 } 107 int main(){ 108 freopen("fragment.in","r",stdin); 109 freopen("fragment.out","w",stdout); 110 int num;cin>>num; 111 int T; 112 cin>>T; 113 while(T--){ 114 inital_array(); 115 cin>>n>>m; 116 string temp; 117 for(int i=1;i<=n;i++){//预处理行列里面每个字母出现的次数和集合 118 cin>>temp; 119 for(int j=1;j<=m;j++){ 120 g[i][j]=temp[j-1]; 121 jihehang[i][g[i][j]-'A'+1]++; 122 jihelie[j][g[i][j]-'A'+1]++; 123 } 124 } 125 for(int i=1;i<=n;i++){//计算行列之间是不是互相对应的 126 for(int j=i+1;j<=n;j++){ 127 equal_hang[i][j]=equal_hang[j][i]=check_equal_hang(i,j); 128 } 129 } 130 for(int i=1;i<=m;i++){ 131 for(int j=i+1;j<=m;j++){ 132 equal_lie[i][j]=equal_lie[j][i]=check_equal_lie(i,j); 133 } 134 } 135 dfsX((n+1)/2,n&1); 136 if(flag_of_solution)cout<<"YES"<<'\n'; 137 else cout<<"NO"<<'\n'; 138 } 139 return 0; 140 }
本地过了就是过了,超时说明OJ有问题
over