题目
给定 1 个 1000 行×20 列的 0-1 矩阵,对于该矩阵的任意 1 列,其中值为 1 的元素的数量不 超过 10%。设有两个非空集合 A 和 B,每个集合由矩阵的若干列组成。
集合 A 和 B 互斥是指 对于矩阵的任意一行,同时满足下列 2 个条件:
1)若 A 中有一个或多个元素在这一行上的 值是 1,则 B 中的元素在这一行全部是 0;
2)若 B 中有一个或多个元素在这一行上的值是 1,则 A 中的元素在这一行全部是 0。
请你设计一个算法,找出一对互斥集合 A 和 B,使得 A 和 B 包含的列的总数最大。
思路
每到一列,都可以选择归入A或者归入B或者谁都不属于。深度为列,因此回溯的深度为20层,每一层有3个选择。
适当地选取剪枝函数和限界函数,减少搜索的空间。
- 剪枝函数:即题目要求,只有互斥才能进入下一层。
- 限界函数:目前A和B矩阵的列数加上剩余的列数已经小于当前最优解,放弃向下搜索。
这里的代码可以优化,具体的优化看评论里有人给出了,就是可以提前计算出每一列之间的互斥关系存入一个n*n的数组里,不再需要每次都循环1000次去判断是否互斥。
代码 原版
#include <iostream>
#include <fstream>
using namespace std;
class Matrix {
private:
int array[1000][20];
int belong[20]; //1属于A 2属于B
int solve[20]; //解
int forbiden[1000]; //1代表被A占据 2代表被B占据
int best; //最佳解
int sumA; //记录现在最佳
int sumB;
int cha;
public:
Matrix(ifstream &afile) {
for (int i = 0; i < 1000; i++) {
for (int j = 0; j < 20; j++)
afile >> array[i][j];
}
for (int i = 0; i < 20; i++) {
belong[i] = 0;
solve[i] = 0;
}
for (int i = 0; i < 1000; i++) {
forbiden[i] = 0;
}
best = 0;
sumA = 0;
sumB = 0;
cha = 0;
}
int CouldBeA(int j) { //第j列能否成为A
for (int i = 0; i < 1000; i++) {
if (forbiden[i] == 2 && array[i][j] == 1)
return 0;
}
return 1;
}
int CouldBeB(int j) { //第j列能否成为B
for (int i = 0; i < 1000; i++) {
if (forbiden[i] == 1 && array[i][j] == 1)
return 0;
}
return 1;
}
void biggestdivide(int i) {
if (i > 19) {
if (sumA + sumB >= best && sumA && sumB && sumA > sumB && sumA - sumB > cha) {
best = sumA + sumB;
cha = sumA - sumB;
for (int i = 0; i < 20; i++) {
solve[i] = belong[i];
}
}
return;
}
if (CouldBeA(i)) { //第I列当成A
int change[1000];
for (int j = 0; j < 1000; j++) {
change[j] = forbiden[j]; //原来的forbiden_B
}
for (int j = 0; j < 1000; j++) {
if (array[j][i] == 1) {
forbiden[j] = 1;
}
}
belong[i] = 1;
sumA++;
biggestdivide(i + 1);
for (int j = 0; j < 1000; j++) {
forbiden[j] = change[j];
}
belong[i] = 0;
sumA--;
}
if (CouldBeB(i)) {
int change[1000];
for (int j = 0; j < 1000; j++) {
change[j] = forbiden[j]; //原来的forbiden_A
}
for (int j = 0; j < 1000; j++) {
if (array[j][i] == 1) {
forbiden[j] = 2;
}
}
belong[i] = 2;
sumB++;
biggestdivide(i + 1);
for (int j = 0; j < 1000; j++) {
forbiden[j] = change[j];
}
belong[i] = 0;
sumB--;
}
if (sumA + sumB + 19 - i >= best)
biggestdivide(i + 1);
}
void show() {
for (int i = 0; i < 20; i++) {
if (solve[i] == 1)
cout << i << ' ' ;
}
cout << endl;
for (int i = 0; i < 20; i++) {
if (solve[i] == 2)
cout << i << ' ';
}
cout << endl;
}
};
int main() {
ifstream aflie;
aflie.open("D:/xxxxxx(your place)", ios::in);
int n;
aflie >> n;
for (int i = 0; i < n; i++) {
Matrix test(aflie);
test.biggestdivide(0);
test.show();
}
aflie.close();
return 0;
}
每一次进入下一层,判断是否满足互斥条件可以不需要再遍历每一行,通过judge的二维数据即可。
thanks for 优化后的代码
#include <iostream>
#include <fstream>
using namespace std;
class Matrix {
private:
int array[1000][20];
int belong[20]; //1属于A 2属于B
int solve[20]; //解
int best; //最佳解
int sumA; //记录现在最佳
int sumB;
int cha;
int judge[20][20]; //减少时间的关键,判断两列互斥
public:
Matrix(istream &afile) {
for (int i = 0; i < 1000; i++) {
for (int j = 0; j < 20; j++)
afile >> array[i][j];
}
for (int i = 0; i < 20; i++) {
belong[i] = 0;
solve[i] = 0;
}
for(int i=0;i<20;i++)
{
for(int j=0;j<20;j++)
{
judge[i][j]=1;
judge[j][i]=1;
for(int k=0;k<1000;k++)
{
if(array[k][i]==1 && array[k][j]==1)
{
judge[i][j]=0;
judge[j][i]=0;
break;
}
}
}
}
best = 0;
sumA = 0;
sumB = 0;
cha = 0;
}
int CouldBeA(int j) { //第j列能否成为A
for (int i = 0; i < 20; i++) {
if (belong[i]==2&&judge[i][j]==0)
return 0;
}
return 1;
}
int CouldBeB(int j) { //第j列能否成为B
for (int i = 0; i < 20; i++) {
if (belong[i]==1&&judge[i][j]==0)
return 0;
}
return 1;
}
void biggestdivide(int i) {
if (i > 19) {
if (sumA + sumB >= best && sumA && sumB && sumA > sumB && sumA - sumB > cha) {
best = sumA + sumB;
cha = sumA - sumB;
for (int i = 0; i < 20; i++) {
solve[i] = belong[i];
}
}
return;
}
if (CouldBeA(i)) { //第I列当成A
belong[i] = 1;
sumA++;
biggestdivide(i + 1);
belong[i] = 0;
sumA--;
}
if (CouldBeB(i)) {
belong[i] = 2;
sumB++;
biggestdivide(i + 1);
belong[i] = 0;
sumB--;
}
if (sumA + sumB + 19 - i >= best)
biggestdivide(i + 1);
}
void show() {
for (int i = 0; i < 20; i++) {
if (solve[i] == 1)
cout << i << ' ' ;
}
cout << endl;
for (int i = 0; i < 20; i++) {
if (solve[i] == 2)
cout << i << ' ';
}
cout << endl;
}
};
int main() {
// ifstream aflie;
// aflie.open("exp4_data.txt", ios::in);
Matrix test(cin);
test.biggestdivide(0);
test.show();
// aflie.close();
return 0;
}