Maximum Clique HDU - 1530 dfs+dp优化

POJ 同时被 3 个专栏收录
113 篇文章 0 订阅
22 篇文章 0 订阅
36 篇文章 0 订阅

一、内容

题意:找出一个子图,里面所有的点都相互有边,求最多顶点的个数。

二、思路

  • 简单做法:
    暴力枚举每一个起点进行dfs,将它作为团的第一个点,然后不断的添加节点,直到不能添加为止。add用来判断子团里面的点是否都能到达新添加的节点。
  • dp优化:
    dp[i]: 代表【i,n-1】顶点中最大团顶点的个数。我们从n-1开始每次增加一个点来更新dp[],那么可知它必定是单调递减的(从1~n-1)。
    e[]: 由于我们是从后往前进行点的枚举,那么我们每次枚举的点为u,那么e[]数组里面存放的就是u能到达的点下标。
  • 剪枝:
    有了dp数组后,我们就可以利用它来进行剪枝。cnt为当前已经得到的答案 + dp【i】<= ans 就不用继续进行搜索了,因为加上最优的答案都比ans小。

三、代码

  • 简单做法:

    #include <cstdio>
    const int N = 50;
    int n, ans, g[N][N], add[N]; //add[]表示有多少个点已经添加进团里 
    
    bool ok(int to, int num) {
    	for (int i = 1; i <= num; i++) {
    		if (!g[add[i]][to]) {
    			//代表这个点to不能到团里面的点
    			return false; 
    		}
    	}
    	return true;
    } 
    
    //shen 表示shen下还有多少个点 
    void dfs(int u, int num, int shen) {
    	if (num > ans) ans = num; //更新答案
    	if (num + shen < ans) return; //最优性剪枝:已经得到的答案+剩下的点数 都比已得到的答案小 
    	//重复性剪枝:从上一个点的后面开始枚举 
    	int t = 1; //代表经过多少个点 shen - t 代表剩下的点  
    	for (int i = u + 1; i < n; i++, t++) {
    		if (ok(i, num)) {
    			//那么这个点可以加进来
    			add[num + 1] = i;
    			dfs(i, num + 1, shen - t); 
    		}
    	} 
    }
    int main() {
    	while (scanf("%d", &n), n) {
    		for (int i = 0; i < n; i++) {
    			for (int j = 0; j < n; j++) {
    				scanf("%d", &g[i][j]);
    			} 
    		} 
    		//枚举每个点做为团的第一个点开始
    		ans = 0;
    		for (int i = 0; i < n; i++) {
    			add[1] = i; //将第一个点添加进去
    			dfs(i, 1, n - 1);  
    		} 
    		printf("%d\n", ans);
    	} 
    	return 0;
    } 
    
    
  • dp优化

    #include <cstdio>
    const int N = 50;
    int n, ans, g[N][N];
    int dp[N]; //dp[i]表示顶点【i,i+1,....n-1】中最大团中的顶点个数
     
    bool dfs(int e[], int eNum, int cnt) {
    	if (cnt > ans) ans = cnt;
    	if (eNum == 0) {
    		//这里代表是最大团中最后一个节点, 它不会又其他边了 所以eNum == 0
    		if (cnt > ans) {
    			ans = cnt;
    			return true; //代表能够更新上一次的答案 
    		} 
    		return false;
    	}
    	//从e[]数组里面的边进行枚举 里面的点到其他在e[]中的点存放在newE中
    	int newE[n];
    	for (int i = 0; i < eNum; i++) {
    		if (cnt + eNum - i <= ans) return false; //代表现在已经得到的答案 + 剩下的点比ans小 
    		if (cnt + dp[e[i]] <= ans) return false; //代表现在已经得到的答案 + [e[i], e[eNum - 1]]求得的最大团数目 都比ans小
    		//然后求出e[i]这个点 能到的其他点放入newE[]中
    		int k = 0;
    		for (int j = i + 1; j < eNum; j++) {
    			if (g[e[i]][e[j]]) newE[k++] = e[j];
    		} 
    		if (dfs(newE, k, cnt + 1)) return true;//代表这条路能更新ans 剩下的直接return 
    	}  
    	return false;
    } 
     
    void f() {
    	int e[N]; //e[]存放u顶点能够到达的其他顶点 
    	ans = 0; //初始答案 
    	for (int i = n - 1; i >= 0; i--) {
    		//从最后一个顶点开始,选择当前点为团中的第一个点
    		int k = 0;
    		for (int j = i + 1; j < n; j++) {
    			//在【i + 1, n - 1】这些点中求出起点能够到的其他点
    			if (g[i][j]) e[k++] = j; 
    		} 
    		//从i开始进行搜索 
    		dfs(e, k, 1);
    		dp[i] = ans; //更新dp中保存的最大团个数 
    		//因为dp[i]只能等于 dp[i + 1] || dp[i+1] + 1, 所以当ans更新了那么必然就是dp[i+1] + 1, 没有更新就是dp[i+1] 
    	} 
    	printf("%d\n", ans);
    }
    
    int main() {
    	while (scanf("%d", &n), n) {
    		for (int i = 0; i < n; i++) {
    			for (int j = 0; j < n; j++) {
    				scanf("%d", &g[i][j]);
    			} 
    		} 
    		f();
    	} 
    	return 0;
    } 
    
  • 4
    点赞
  • 4
    评论
  • 1
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值