回溯法说来简单,写起来难,真的是要愁死。
回溯法有两种模板--子集树、排列树
5.4符号三角形--dfs
![](https://i-blog.csdnimg.cn/blog_migrate/8d38077f70ea3fd0f0c742e580a39c86.png)
计算多少个满足条件的符号三角形,同号下面为“+”,异号下面为“-”。
根据异或的规则我们令“+”=0,“-”=1,(异或的运算规则:相同取0,相异取1)正好异或的规则满足题意,并且方便计数。
0^0=0 | 0^1=1 |
1^1=0 | 1^0=1 |
我们知道,第一行的符号确定了,后面的都确定了。所以需要遍历第一行的所有的“+”号和“-”号。
第一行一共有n和符号,变幻每个位置的符号,就是个叶子。
观察发现:满足条件的所有三角形中除了初始n=3个字符的三角形,其他所有满足结果要求的子三角形中,+和-的个数都是相同的。(但是最后输出的时候n=3有四种结果,也是对的,不知道为什么我第一次想的时候,这个n=3怎么不对了,可能是脑子有点小毛病)
下面列出n=3的四种情况
0 0 1 0 1 1 + + - + - - | 0 1 0 1 1 0 + - + - - + | 1 0 0 1 0 1 - + + - + - | 1 1 1 0 0 0 - - - + + + |
回溯的主要思想:
用n元组x[1:n]表示符号三角形的第一行的n个符号,当x[i]=1时,表示符号三角形的第一行的第i个符号为“+”;当x[i]=0时,表示符号三角形的第一行的第i个符号为“-”;1<=i<=n。
由于x[i]是二值的。所以在用回溯法解符号三角形问题时,可以用一棵完全二叉树来表示其解空间。
在符号三角形的第一行的前i个符号x[1:i]确定后,就确定了一个有i(i+1)/2个符号组成的符号三角形。
下一步确定x[i+1]的值后,只要在前面已确定的符号三角形的右边加一条边,就可以拓展为x[1:i+1]所对应的符号三角形。最终由x[1:n]所确定的符号三角形包含的“+”个数与“-”同为n(n+1)/4.
因此,在回溯可将“+”、“-”个数均不超过n(n+1)/4为约束条件。
同时,对于给定的n当n(n+1)/2为奇数时,显然不存在“+”和“-”个数相同的符号三角形。
剪枝:不等的直接return,如果cnt>half || (t*(t-1)/2-cnt)>half,当前的第一行第t个符号以前所对应的三角形,减号的个数大于一半了或者是加号的个数大于一半了都直接return。另外一个是要特判初始的总数,奇数肯定是不行的。
然后注意回溯的时候对sum值进行修改。剩下的就是dfs深度优先搜索的功底了。
#include <iostream>
using namespace std;
int n;//n行,第一行有n个元素
int sum=0;//记录满足题意的数
int half;//n*(n+1)/4
int cnt;//'-'的个数
int p[100][100]; //符号三角形矩阵
//输出结果
void Output(){
cout<<"------------\n";
for(int i=1;i<=n;i++){
for(int j=1;j<=n-i+1;j++){
cout<<p[i][j]<<" ";
}
cout<<endl;
}
//为了形象看结果,还可以这样输出
for(int i=1;i<=n;i++){
for(int k=1;k<=i;k++){
cout << " ";
}
for(int j=1;j<=n-i+1;j++){
if(p[i][j]==0) cout << "+" << " ";
else if (p[i][j]=1) cout << "-" << " ";
}
cout << endl;
}
}
//dfs主函数
void BackTrack(int t){ //第t行
// cout<<"cnt="<<cnt<<" half="<<half<<endl;
if(cnt>half || (t*(t-1)/2-cnt)>half) return;//如果加号和减号不相等直接return
//搜索到了叶子,生成了第n个符号,又是一个满足条件的符号三角形
if(t>n){
Output();
sum++;
}
else{
//第一行的第t个叶子,试探,两种结果: 符号三角形的矩阵 p[1][t] = 0 和 p[1][t] = 1;
for(int i=0;i<=1;i++){
p[1][t] = i;
cnt += i;//'-'的个数
//计算前t个叶子以及他们对应的符号三角形的"+""-"号,相同为"+"=0,相异取"-"=1
for(int j=2;j<=t;j++){
p[j][t-j+1] = p[j-1][t-j+1] ^ p[j-1][t-j+2];//^相同取0,不同取1
cnt+=p[j][t-j+1];
}
BackTrack(t+1);
//撤销操作,换源回溯现场,以便下一次回溯
for(int j=2;j<=t;j++){
cnt-=p[j][t-j+1];
}
cnt -= i;
}
}
}
int main(){
// cin>>n;
n=4;
half=n*(n+1)/2;//共有n*(n+1)/2个符号
if(half%2==1){ //奇数个符号的话,不存在'+'的个数等于'-'的个数
cout<<"无解\n";
}
else{
half=half/2;
BackTrack(1);
cout<<"sum="<<sum<<endl;
}
return 0;
}
//为了方便调试,令n=4