• POJ 3977 Subset 折半枚举+二分搜素+双指针


    一、题目大意

    我们有N(N<=35)个元素,从中选取一个子集,使得它的元素求和的绝对值最小,如果有多个可行解,选择元素最小的。

    输出最优子集的元素总和绝对值,和最优子集元素的数量。

    二、解题思路

    我们把前一半,后一半数组分开考虑。

    我们利用二进制递增的思路(0001,0010,0011...1111),把后一半数组的所有子集求和给算出来(去掉空集),同时记录每个子集的元素数量

    之后根据子集的sum进行排序,然后利用双指针,把所有sum相等的子集的元素数量更新为 同等sum下最小的元素数量。

    然后利用二进制枚举前半部分数组(包括空集),对于每一个左半部分的元素和leftSum,去后半部分数组里二分找 -leftSum,(这个二分的思想就是找到后半部分最小子集元素和不小于 -leftSum的第一个下标),然后把二分的结果idx和idx-1都判断下,计算左右子集和的绝对值,和元素数量。更新ans。

    需要注意的是我们去掉了右边为空集的情况,所以要额外判断下只使用左边元素的情况。

    我也是很菜了,这个题目WA了60多次,写了6天,最后绝对不用pair了,自己写结构体,也不用lower_bound了,自己写二分,然后再结合自己想出来的尺取法,过了这道题。

    过程中没有查看题解,没有搜过答案,但是去看了STL中pair的源码和Comparator的源码,还看了下《挑战程序设计》的“超大背包问题”的源码,其实不应该去看这些,影响到了进步和思考的进程,看完STL源码和白书后决定不用pair和lower_bound了,手写二分底层实现,手写双指针优化,终究是过了。

    可以说我是非常菜了,自己摸爬滚打总结的一套代码分享在下面。

    过了以后查过题解,我这个他们那个map那个复杂一些,因为用了自定义结构体,双指针和手写二分底层实现,但是比它快了5倍吧,源码分享给大家。

    三、代码

    1. #include
    2. #include
    3. using namespace std;
    4. typedef long long ll;
    5. struct Node
    6. {
    7. int cnt;
    8. ll sum;
    9. Node(ll sum = 0LL, int cnt = 0) : sum(sum), cnt(cnt) {}
    10. };
    11. Node rightNodes[262150];
    12. int towPow[27], n, rightLen, leftLen, rightPow, leftPow, ansCnt;
    13. ll num[40], ans, inf = 0x3f3f3f3f3f3f3f3fLL;
    14. void initTwoPow()
    15. {
    16. towPow[0] = 1;
    17. for (int i = 1; i <= 21; i++)
    18. {
    19. towPow[i] = towPow[i - 1] * 2;
    20. }
    21. }
    22. bool compareNode(const Node &a, const Node &b)
    23. {
    24. return a.sum < b.sum;
    25. }
    26. ll absVal(ll a)
    27. {
    28. if (a >= 0LL)
    29. {
    30. return a;
    31. }
    32. else
    33. {
    34. return a * (-1LL);
    35. }
    36. }
    37. void input()
    38. {
    39. ans = 0LL;
    40. for (int i = 0; i < n; i++)
    41. {
    42. scanf("%lld", &num[i]);
    43. ans = ans + num[i];
    44. }
    45. ans = absVal(ans);
    46. ansCnt = n;
    47. leftLen = n / 2;
    48. rightLen = n - leftLen;
    49. leftPow = towPow[leftLen];
    50. rightPow = towPow[rightLen];
    51. }
    52. void calcRightSubsetBesideEmptySet()
    53. {
    54. for (int i = 1; i < rightPow; i++)
    55. {
    56. rightNodes[i - 1].sum = 0LL;
    57. rightNodes[i - 1].cnt = 0;
    58. for (int j = 0; j < rightLen; j++)
    59. {
    60. if ((i & towPow[j]) == towPow[j])
    61. {
    62. rightNodes[i - 1].sum = rightNodes[i - 1].sum + num[leftLen + j];
    63. rightNodes[i - 1].cnt = rightNodes[i - 1].cnt + 1;
    64. }
    65. }
    66. }
    67. rightNodes[rightPow - 1].sum = inf;
    68. rightNodes[rightPow - 1].cnt = n + 1;
    69. sort(rightNodes, rightNodes + rightPow, compareNode);
    70. }
    71. void minimizeCntByTwoPosinter()
    72. {
    73. int l = 0, r = 1, optCnt = -1;
    74. while (true)
    75. {
    76. while (r < rightPow && rightNodes[r].sum != rightNodes[l].sum)
    77. {
    78. l++;
    79. r++;
    80. }
    81. optCnt = rightNodes[l].cnt;
    82. while (r < rightPow && rightNodes[r].sum == rightNodes[l].sum)
    83. {
    84. optCnt = min(optCnt, rightNodes[r].cnt);
    85. r++;
    86. }
    87. while ((l + 1) < r)
    88. {
    89. rightNodes[l++].cnt = optCnt;
    90. }
    91. if (r == rightPow)
    92. {
    93. break;
    94. }
    95. }
    96. }
    97. int binarySearch(ll leftSum)
    98. {
    99. int l = -1, r = rightPow;
    100. while (l + 1 < r)
    101. {
    102. int mid = (l + r) / 2;
    103. if (rightNodes[mid].sum < leftSum)
    104. {
    105. l = mid;
    106. }
    107. else
    108. {
    109. r = mid;
    110. }
    111. }
    112. return (l + 1);
    113. }
    114. void solve()
    115. {
    116. ll lSum = 0LL;
    117. int lCnt = 0;
    118. for (int i = 0; i < leftPow; i++)
    119. {
    120. lSum = 0LL;
    121. lCnt = 0;
    122. for (int j = 0; j < leftLen; j++)
    123. {
    124. if ((i & towPow[j]) == towPow[j])
    125. {
    126. lSum = lSum + num[j];
    127. lCnt = lCnt + 1;
    128. }
    129. }
    130. if (lCnt != 0 && absVal(lSum) < ans)
    131. {
    132. ans = absVal(lSum);
    133. ansCnt = lCnt;
    134. }
    135. else if (lCnt != 0 && absVal(lSum) == ans && lCnt < ansCnt)
    136. {
    137. ansCnt = lCnt;
    138. }
    139. int idx = binarySearch(lSum * (-1LL));
    140. if ((idx + 1) < rightPow && absVal(rightNodes[idx].sum + lSum) < ans)
    141. {
    142. ans = absVal(rightNodes[idx].sum + lSum);
    143. ansCnt = rightNodes[idx].cnt + lCnt;
    144. }
    145. else if ((idx + 1) < rightPow && absVal(rightNodes[idx].sum + lSum) == ans && (rightNodes[idx].cnt + lCnt) < ansCnt)
    146. {
    147. ansCnt = rightNodes[idx].cnt + lCnt;
    148. }
    149. idx--;
    150. if (idx >= 0 && absVal(rightNodes[idx].sum + lSum) < ans)
    151. {
    152. ans = absVal(rightNodes[idx].sum + lSum);
    153. ansCnt = rightNodes[idx].cnt + lCnt;
    154. }
    155. else if (idx >= 0 && absVal(rightNodes[idx].sum + lSum) == ans && (rightNodes[idx].cnt + lCnt) < ansCnt)
    156. {
    157. ansCnt = rightNodes[idx].cnt + lCnt;
    158. }
    159. }
    160. }
    161. int main()
    162. {
    163. initTwoPow();
    164. while (true)
    165. {
    166. scanf("%d", &n);
    167. if (n == 0)
    168. {
    169. break;
    170. }
    171. input();
    172. calcRightSubsetBesideEmptySet();
    173. minimizeCntByTwoPosinter();
    174. solve();
    175. printf("%lld %d\n", ans, ansCnt);
    176. }
    177. return 0;
    178. }

  • 相关阅读:
    (二)kafka从入门到精通之kafka的优势
    SQLlabs46关
    大数据旅游数据分析:基于Python旅游数据采集可视化分析推荐系统
    视频教程下载:ChatGPT驱动的SEO、网络营销、生产力提升
    Linux搭建C++开发环境
    阿里妈妈获得商品详情 API 返回值说明
    从面试官角度谈谈面试常见问题,求职者注意避坑!
    01 【入门篇-介绍和安装】
    基于单片机的声光控制节能灯设计
    使用 Java RestClient 与 Elasticsearch 进行索引管理的示例
  • 原文地址:https://blog.csdn.net/qq_53038151/article/details/133194862