题解
- 基础:我们令cnum[i]表示点i到n中的最大团大小,则有cnum[i] = cnum[i + 1] 或cnum[i + 1] + 1,也就是说具有单调性(类似于DP),这样我们可以利用这个性质进行剪枝,有点类似于二分图增广路,倒着每次添加一个点i,看看能否形成更优的答案,如果形成了,那么直接return就好了。另外选点的时候,顺着按下标从小到大选,这样可以避免枚举一些重复情况。
- 剪枝1: 如果当前选取的点数加上剩下还可能选的点数都不能比之前的最优解优秀,那么return;
- 剪枝2:对于即将要选择的点x,如果cnum[x] 加上当前答案都不能比之前的最优解优秀,那么return;因为这说明就算x - n里面的最大团完全可以被添加进来,也不能得到更优的解
- 剪枝3:如基础所说,如果答案发生更新,直接return;
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
using namespace std;
int const N = 50 + 10;
int n,g[N][N],cnum[N],ans; //cnum表示[i,n]所能形成的最大团
bool dfs(int step,int *r,int len){ //step表示已有的点个数,并且与r集合都相连接,判断r集合的点是否是完全图
if(step > ans){ //根据单调性,cnum[i] = cnum[i+1] + 1 or crum[i],因为每次选择下一个,最大团最多加1
ans = step;
return true;
}
int r2[N],len2;
for(int i=1;i<=len;i++){
if(step + len - i + 1 <= ans) break; //如果当前的最大团加上后面的节点都不可能超过答案
if(step + cnum[r[i]] <= ans) break; //如果当前的最大团加上cnum[r[i]]不能超过答案
len2 = 0;
for(int j=i+1;j<=len;j++)
if(g[r[i]][r[j]]) r2[++len2] = r[j];
if(dfs(step + 1,r2,len2)) return true; //如果最大团扩展,那么直接返回;否则需要遍历下一个点。
}
return false;
}
int main(){
while(~scanf("%d",&n) && n){
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%d",&g[i][j]);
memset(cnum,0,sizeof(cnum));
cnum[n] = 1;
ans = 1;
int r[N],len;
for(int i=n-1;i>=1;i--){
len = 0;
for(int j=i+1;j<=n;j++)
if(g[i][j]) r[++len] = j; //表示与i相连的点
dfs(1,r,len);
cnum[i] = ans;
}
printf("%d\n",ans);
}
return 0;
}