蓝桥杯,算法训练 无聊的逗C++ (利用补集与子集求解)注释详细

问题: 

 逗志芃在干了很多事情后终于闲下来了,然后就陷入了深深的无聊中。不过他想到了一个游戏来使他更无聊。他拿出n个木棍,然后选出其中一些粘成一根长的,然后再选一些粘成另一个长的,他想知道在两根一样长的情况下长度最长是多少。

输入格式

  第一行一个数n,表示n个棍子。第二行n个数,每个数表示一根棍子的长度。

输出格式

  一个数,最大的长度。

样例输入

4
1 2 3 1

样例输出

3

数据规模和约定

  n<=15

思路:

        观察这道题,我们可以尝试很多种方法,但是有一种经典的方法是,把每个木棍转化为二进制思想。比如现在有四根木棍,就可以看作有四位二进制数(0000),然后就可以用0、1分别表示没选和选了的小木棍,0110就是第2、第3的小木棍。所以从0000-1111一共16种选小木棍的情况,我们可以把每种情况对应的木棍长度加起来,相当于粘成了第一个大木棍。剩下的就是找上面16种,每个情况对应的补集的子集,这个就是第二个大木棍能粘成长度的情况。还不清楚的可以自己动手在草稿纸上画一画,细心理解。

        举个例子:现在有四个小木棍,对应的长度为 1 2 3 1

        一种情况是选了第2和第4小木棍(1010)粘成第一个大木棍,其补集为0101,就是还剩第1和第3个小木棍,然后补集的子集就是  1、3  小木棍的组合情况,可以只拿  1  (0001)小木棍,也可以只拿  3  (0100)小木棍,最后还可以     1、3  (0101)木棍一起拿,结果就是补集的子集有三种情况0001,0100,0101,这3种情况都可以粘合成第二个大木棍。也就是说,第一个大木棍有16种情况,第二个大木棍的情况则在第一个大木棍情况的补集里,可以在草稿纸上画一画形象理解。

代码及其解析:

代码中涉及位运算(<<)和与运算(&),1<<n相当于1*2^n。与运算 0101&0001=0001、0110&1001=0000、1010&0010=0010。这些基本运算符常识应该要清楚。

不清楚的循环运算用草稿纸一一列出来,就明白了

#include<iostream>
using namespace std;

#define N 70000     //得容纳2^15种情况

int arr[N] = { 0 }, b[N] = { 0 };

int main() {
	int n, temp, ans = 0;

	cin >> n;                //下面我们假设 n = 4

	for (int i = 0; i < n; i++) {
		cin >> b[i];
	}
                                            
	for (int i = 0; i < (1 << n); i++) {//抽取0到15的情况,0000(一个都不取)-1111(全取)
		for (int j = 0; j < n; j++) {
			if ((i & (1 << j))) {      //当j=2,判断有哪根木棍(1010)&(0010)=0010,拥有2木棍
				arr[i] += b[j];		//i的二进制,为1的就是选了该木棍,把选了的加起来
			}
		}
	}

	for (int i = 0; i < (1 << n); i++) {
		//temp是i的补集,0001的补集是1110。自己验证
		temp = (1 << n) - 1 - i;		

		//枚举出tem的子集,相当于选定i后,列出第二根合成长度的所有情况
        //for (int j = 0; j <= temp; j++) {
		//	if ((i&j) == 0 && arr[i] == arr[j]) {			
		//		ans = max(ans, arr[i]);
		//	}
        //}
        //这个式子求子集更快,避免重复比较
        for (int j = temp; j; j = (j - 1) & temp) {
			if (arr[i] == arr[j]) {			
				ans = max(ans, arr[i]);
			}
         }
	}

	cout << ans << endl;

	return 0;
}

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Kernighan-Lin算法是一种贪心算法,用于解决MaxCut问题。它的基本思想是不断地将节点划分为两个子集,使得子集之间的边权和最大化。 以下是Kernighan-Lin算法C++代码实现: ```c++ #include <iostream> #include <vector> #include <algorithm> #include <numeric> using namespace std; // 计算节点的度数 vector<int> calc_degrees(const vector<vector<int>>& graph) { vector<int> degrees(graph.size(), 0); for (int i = 0; i < graph.size(); ++i) { degrees[i] = accumulate(graph[i].begin(), graph[i].end(), 0); } return degrees; } // 计算节点的初始划分 vector<int> calc_initial_partition(const vector<int>& degrees) { vector<int> partition(degrees.size(), 0); int sum_degrees = accumulate(degrees.begin(), degrees.end(), 0); int half_sum_degrees = sum_degrees / 2; int curr_sum_degrees = 0; for (int i = 0; i < degrees.size(); ++i) { curr_sum_degrees += degrees[i]; if (curr_sum_degrees > half_sum_degrees) { partition[i] = 1; } } return partition; } // 计算节点之间的边权和 int calc_cut_size(const vector<vector<int>>& graph, const vector<int>& partition) { int cut_size = 0; for (int i = 0; i < graph.size(); ++i) { for (int j = 0; j < graph[i].size(); ++j) { if (partition[i] != partition[graph[i][j]]) { cut_size += 1; } } } return cut_size; } // 计算节点的移动增益 vector<int> calc_gain(const vector<vector<int>>& graph, const vector<int>& partition, const vector<int>& degrees) { vector<int> gain(graph.size(), 0); for (int i = 0; i < graph.size(); ++i) { int cut_size_diff = 0; for (int j = 0; j < graph[i].size(); ++j) { if (partition[i] != partition[graph[i][j]]) { cut_size_diff -= 1; } else { cut_size_diff += 1; } } gain[i] = cut_size_diff - 2 * degrees[i] * (partition[i] - 0.5); } return gain; } // 交换节点的划分 void swap_partition(vector<int>& partition, int i, int j) { int tmp = partition[i]; partition[i] = partition[j]; partition[j] = tmp; } // Kernighan-Lin算法 void kernighan_lin(const vector<vector<int>>& graph, vector<int>& partition) { vector<int> degrees = calc_degrees(graph); int cut_size = calc_cut_size(graph, partition); bool improved = true; while (improved) { improved = false; vector<int> gain = calc_gain(graph, partition, degrees); vector<int> moved(graph.size(), 0); for (int i = 0; i < graph.size(); ++i) { if (moved[i]) { continue; } int max_gain = 0; int max_gain_idx = -1; for (int j = 0; j < graph[i].size(); ++j) { int k = graph[i][j]; if (!moved[k] && gain[k] > max_gain) { max_gain = gain[k]; max_gain_idx = k; } } if (max_gain_idx != -1) { swap_partition(partition, i, max_gain_idx); cut_size += max_gain; moved[i] = true; moved[max_gain_idx] = true; improved = true; } } } } // 测试代码 int main() { vector<vector<int>> graph = {{1, 2}, {0, 2, 3}, {0, 1, 3}, {1, 2}}; vector<int> partition = calc_initial_partition(calc_degrees(graph)); kernighan_lin(graph, partition); cout << calc_cut_size(graph, partition) << endl; return 0; } ``` 在上面的代码,我们首先计算了每个节点的度数,然后根据节点的度数计算了初始的划分。接着,我们不断交换节点的划分,直到无法继续改进为止。在交换节点的划分时,我们选择移动增益最大的节点。最后,我们计算了最终的划分对应的边权和。 使用Kernighan-Lin算法可以在多项式时间内解决MaxCut问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值