问题:
逗志芃在干了很多事情后终于闲下来了,然后就陷入了深深的无聊中。不过他想到了一个游戏来使他更无聊。他拿出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;
}