实验题目
符号三角形的 第1行有n个由“+”和“-”组成的符号 ,以后每行符号比上行少1个,2个同号下面是“+”,2个异号下面是“-” 。计算有多少个不同的符号三角形,使其所含“+” 和“-” 的个数相同。
OJ代码——Accepted:
#include <iostream>
using namespace std;
class Triangle
{
private:
int n; //第一行的符号数量
int **a;//存储+ -号,0表示+,1表示-
int coun;//记录+号数量,根据+号数量,可以用i*(i+1)/2-coun求出减号数量
int num;//记录+与-数量相等且等于num*(num+1)/4的情况
const int half;//加号=减号=总符号数/2
public:
Triangle(int n) :half(n*(n + 1) / 4) //构造函数
{
this->n = n;
coun = 0;
num = 0;
a = new int*[n + 1];
for (int i = 0; i <= n; i++)
a[i] = new int[n + 1];//开辟一个n*n的矩阵,用于在回溯过程中记录三角形符号分布
}
void triangleSolve()//调用回溯算法,并且先判断n*(n+1)/2是否为奇数
{
if (n*(n + 1) / 2 % 2 == 0)
{
find(1); //调用回溯算法
display();
}
else
{
cout << 0 << endl;
return;
}
}
void find(int m)//回溯核心算法
{
if (m > n)
{
num++; //找到的解法++
return;
}
for (int i = 0; i < 2; i++)
{
a[1][m] = i; //第一行第m个符号
coun += i; //符号统计
for (int j = 2; j <= m; j++)
{
a[j][m - j + 1] = a[j - 1][m - j + 1] ^ a[j - 1][m - j + 2]; //异或运算符查看关系
coun += a[j][m - j + 1]; //记录1 即‘-’的个数
}
if ((coun <= half) && (m*(m + 1) / 2 - coun <= half)) //约束函数
find(m + 1); //符合条件继续寻找
for (int j = 2; j <= m; j++) //恢复现场
coun -= a[j][m - j + 1]; //减去新加入的右侧边的+数量
coun -= i; //减去m位置的+的数量
}
}
void display()
{
cout << num << endl;
}
};
int main()
{
int n;
cin >> n;
Triangle test(n);
test.triangleSolve();//解决问题
system("pause");
return 0;
}
实验截图:
解题思路:
1、 因为计算含有“+”和“-”相同的符号三角形的数量与第一行符号有关,不断改变第一行每个符号,并搜索符合条件的解可以使用回溯算法。
2、 为了便于计算,我们用0和1分别代替“+”和“-”,此时可以直接运用异或运算符来判断符号三角形的关系,即:+ +为 + :“1^1=0”,- - 为+ :“0^0=0”,+ - 为- :“1^0=1”,- + 为- :“0^1=1”,在代码中体现为:
a[j][m - j + 1] = a[j - 1][m - j + 1] ^ a[j - 1][m - j + 2]
3、 不进行回溯的条件:1)所有符号总数为奇数,不存在“+”和“-”数量相等的情况;2)“+”或“-”的数量超过总数的一半
算法分析:
想要求得整个三角形中“+”和“-”数量相等的三角形数量只与三角形的第一行的符号有关,故而可以逐次安排第一行的每一个符号,由于每次安排一个符号,就会在原定的三角形中最右侧加一条三角形边,故而采用回溯法,逐次对第一行每个位置的符号进行选择,并且扩充新的三角形边。
为了在回溯过程中及时的计算出三角形的最新状况,采用二维数组存储三角形,根据图形的关系,可以求出三角形后一行与前一行的关系,并且加入到二维数组中。注意:在每次回溯完成之后都要进行恢复现场处理,即:coun -= a[j][m - j + 1];。
为了更好地实现回溯算法,必须存在剪枝语句,这里使用“+”或者“-”都小于总数的一半作为约束,即:(coun <= half) && (m*(m + 1) / 2 - coun <= half),只要函数可以递归到n+1个字符,说明前n个字符的安排均满足以上约束条件,即“+”和“-”数量相等,找到的三角形数量加一。
法二:
不采用定义类的方式,并且输出每个符合条件的符号三角形。
实验代码:
#include"iostream"
using namespace std;
char cc[2] = { '+','-' }; //便于输出
int n, half; //全部符号总数一半
int counter; //1计数,即“-”号计数
char **p; //符号存储空间
long sum; //符合条件的三角形计数
void Backtrace(int t) //t,第一行第t个符号
{
int i, j;
if (t > n)
{//符号填充完毕
sum++;
cout << "第" << sum << "个:" << endl;
for (i = 1; i <= n; ++i) //打印符号
{
for (j = 1; j < i; ++j)
cout << " ";
for (j = 1; j <= n - i + 1; ++j)
cout << cc[p[i][j]] << " ";
cout << endl;
}
}
else
{
for (i = 0; i < 2; ++i)
{
p[1][t] = i; //第一行第t个符号
counter += i; //“-”号统计
for (j = 2; j <= t; ++j) //当第一行符号>=2时,可以运算出下面行的某些符号
{
p[j][t - j + 1] = p[j - 1][t - j + 1] ^ p[j - 1][t - j + 2];//异或运算
counter += p[j][t - j + 1];
}
if ((counter <= half) && (t*(t + 1) / 2 - counter <= half)) //约束条件
Backtrace(t + 1); //在第一行增加下一个符号
for (j = 2; j <= t; ++j) //恢复现场
counter -= p[j][t - j + 1];
counter -= i;
}
}
}
int main()
{
cin >> n;
counter = 0;
sum = 0;
half = n * (n + 1) / 2;
int i = 0;
if (half % 2 == 0)//总数须为偶数,若为奇数则无解
{
half /= 2; //+ -相等时的数量
p = new char *[n + 1];
for (i = 0; i <= n; ++i)
{
p[i] = new char[n + 1];
memset(p[i], 0, sizeof(char)*(n + 1));
}
Backtrace(1);
}
cout << sum << endl;
system("pause");
return 0;
}
实验截图: