ZOJ - 1492 dfs剪枝求最大团

题意:

要求一个不超过50个点的图中最大完全子图(团)的大小。

思路:

dfs + 剪枝 + 记忆化

所谓团,就是一个完全子图,也就是在一个图中找到一个子图,满足任意子图中两个点都有边相连,最大团顾名思义就是点最多的团。

NP-hard问题,没什么思路不妨考虑搜索,按照团的定义,我们可以每次任意选择一个点开始当作一个集合U,然后每次找到一个点满足和集合U中的所点都有边相连,这样的点就可以加入集合U,团的大小就增加了1。这是最基本的思路,就是枚举所有的完全子图,为了避免枚举的重复,我们可以按照点来排序,每次从i个点之后搜索更新集合U。

初始化的时候任意选择一个点u,然后将所有比u大且和u相连的点加入到集合st[1],然后进行第一层dfs,在st[1]中选择一个点v,将st[1]中比v大的且和v相连的点都加入集合st[2],再进行下一层dfs...最后终止的条件是这里某一层的st[x]集合为空集,此时说明这个团无法再增大了,此时更新最大值。

当然仅仅是这样的思路肯定会超时的,这时候我们需要记忆化+剪枝。用dp[i]保存第i个点以后的点能得到的最大团的大小,免于重复计算。

有两个很重要的剪枝:

剪枝1:如果 U 集合中的点的数量+1(选择 ui 加入 U 集合中)+st[i] 中所有 ui 后面的点的数量 ≤ 当前最优值,就退出

剪枝2:如果 U 集合中的点的数量+1(理由同上)+[ui, n]这个区间中能构成的最大团的顶点数量 ≤ 当前最优值,就退出

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int MAXN = 105;

struct MaxClique {
	bool g[MAXN][MAXN];
	int n, dp[MAXN], st[MAXN][MAXN], ans;
	//	dp[i]表示第i个点之后能组成的最大团的大小,
	// 	st[i][j]表示算法中第i层dfs所需要的点的集合,保存有可能是最大团其中之一的点 

	void init(int n) {
		this->n = n;
		memset(g, false, sizeof(g));
	}

	void addedge(int u, int v, int w) {
		g[u][v] = w;
	}

	bool dfs(int sz, int num) {
		if (sz == 0) {
			if (num > ans) {
				ans = num;
				return true;
			}
			return false;
		}
		for (int i = 0; i < sz; i++) {		// 在第num层的集合中枚举一个点i
			if (sz - i + num <= ans) return false;	// 剪枝1
			int u = st[num][i];
			if (dp[u] + num <= ans) return false;	// 剪枝2
			int cnt = 0;
			for (int j = i + 1; j < sz; j++) {	// 在第num层遍历在i之后的且与i所相连的点,并且加入第num+1层集合
				if (g[u][st[num][j]]) 
					st[num + 1][cnt++] = st[num][j];
			}
			if (dfs(cnt, num + 1)) return true;
		}
		return false;
	}

	int solver() {
		ans = 0;
		memset(dp, 0, sizeof(dp));
		for (int i = n; i >= 1; i--) {
			int cnt = 0;
			for (int j = i + 1; j <= n; j++) {	// 初始化第1层集合
				if (g[i][j]) st[1][cnt++] = j;
			}
			dfs(cnt, 1);
			dp[i] = ans;
		}
		return ans;
	}

}maxclique; 

int main() {
	int n;
	while (scanf("%d", &n), n) {
		maxclique.init(n);
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= n; j++) {
				int x;
				scanf("%d", &x);
				maxclique.addedge(i, j, x);
			}
		}
		printf("%d\n", maxclique.solver());
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值