题解 - 洛谷1441
这是一道状压 DP 题,我厚颜无耻地看过题解发现其中有些卡常技巧惊为天人,于是我把它总结、记录了一下。当然,不敢发出去审核。。。
思路
题目要求从 \(n\) 个砝码里面去掉 \(m\) 个,那很自然的朴素想法是一个DFS
跑过去,然后筛选出一种合法的方案,再套一个01背包
统计一下答案即可
然后分析一下复杂度,达到了
其中第一项是枚举子集的复杂度,之后是01背包方案数
+ 扫一遍
+ 清零
+ 求出背包容量t
的复杂度。
这里参考了 @皎月半洒花 的题解
然后这会 \(\texttt{T}\) 掉。
想着怎么优化。
优化 DFS
把前面提到的筛选合法的方案看成一个01串
,去掉的用 0
,留下来的用 1
,然后直接 for
循环枚举,这样可以卡掉 DFS
递归的常数。
这里的循环从 \(2^{n - m - 1}\) 开始(因为小于 \(n - m\) 个位数根本没有 \(n - m\) 个 \(1\)),到\(2^n - 1\)结束。
然后判断合法性要用到 popcount()
函数,即统计一个数二进制表示下 1
的个数。
这个函数是 gcc
的内置函数,函数名为 __builtin_popcount()
。但CCF禁止使用下划线开头的函数,所以我们自己编一个。
这里我看到了一个效率很高的版本,是在 @pantw 的题解 中展示的。他不用一个一个枚举位数,而是通过 分块
+ 打表
的方式,每 \(4\) 位为一组进行统计。
const int tb[16] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4};
IL int popcnt(ll x) {
int cnt = 0;
for (; x; x >>= 4) cnt += tb[x & 15];
return cnt;
}
其中 tb
数组存的是 \(0 \sim 15\) 二进制位上 \(1\) 的个数。
优化 01背包
嗯,先说说正常的 01背包
该怎么写。
这里参考了 @hsfzLZH1 的题解。
memset(f, 0, sizeof f);
f[0] = true;
ans = 0;
tot = 0; // 清零,因为可能要调用多次
for (int i = 0; i < n; i++) // 从前到后选取所有的砝码
{
if (tf[i]) continue; // 如果被标记为已经舍弃就跳过
for (int j = tot; j >= 0; j--)
if (f[j] && !f[j + a[i]])
f[j + a[i]] = true, ans++; // 否则dp并且维护ans的值
tot += a[i]; // 这个tot意为当前f[i]为真值的最大的i,极大加快了dp过程
}
ret = max(ans, ret); // 更新最后的答案
然后咱们想,能不能也用 01串
表示 DP状态
呢?
于是咱们产生了想法。。。
这里参考了 @皎月半洒花 的题解
bitset<2021> dp;
dp.reset(), dp[0] = 1, t = 0 ;
for (j = 0 ; j < N ; ++ j) t += (1 << j & i) ? base[j + 1] : 0 ;
for (k = 0 ; k < N ; ++ k)
for (j = t ; j >= base[k + 1] ; -- j)
dp[j] = (1 << k & i) ? dp[j] : (dp[j] | dp[j - base[k + 1]]) ;
Ans = max(Ans, (int)dp.count() - 1) ;
哦,对了, 这个最终的 \(-1\) 是把称出了 \(0\) 的重量扣掉。
还不够?
确实。
你看这个 DP
是怎么转移的。由于 dp
是一个 bitset
,在里层的 for
循环以后,从动态上看,这个bitset
所有的 1
都被移动了 base[k + 1]
位(注:这位老哥从1
开始读入)。
因此,我们实际上是可以用位运算压掉这一维!!!
我们写出了如下代码:
这里参考了 @pantw 的题解
bitset<2021> S(1);
for (int j = 0; j < n; j++)
if (i & (1 << j)) S |= S << a[j];
int siz = S.count();
ans = max(ans, siz);
于是我们愉快的通过了这道题。。。
完整代码
这是我自己写的
对着题解打的
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
typedef long long ll;
#define IL inline
template <class I>
IL void read(I &x) {
int fl = 1;
x = 0;
char c = getchar();
while (!isdigit(c)) {
if (c == '-') fl = -1;
c = getchar();
}
while (isdigit(c)) x = x * 10 + c - '0', c = getchar();
x *= fl;
}
int a[100];
const int tb[16] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4};
IL int popcnt(ll x) {
int cnt = 0;
for (; x; x >>= 4) cnt += tb[x & 15];
return cnt;
}
int main() {
#ifdef LOCAL
clock_t c1 = clock();
freopen("in.in", "r", stdin);
freopen("out.out", "w", stdout);
#endif
#ifdef ONLINE_JUDGE
#endif
int n, m, d, ans = 0;
read(n), read(m), d = n - m;
for (int i = 0; i < n; i++) read(a[i]);
for (int i = 1 << (d - 1), ln = 1 << n; i < ln; i++)
if (popcnt(i) == d) {
bitset<2021> S(1);
for (int j = 0; j < n; j++)
if (i & (1 << j)) S |= S << a[j];
int siz = S.count();
ans = max(ans, siz);
}
printf("%d\n", ans - 1);
#ifdef LOCAL
cout << "\nTime used: " << clock() - c1 << "ms" << endl;
#endif
return 0;
}
作者: Reqwey
出处:https://www.cnblogs.com/reqwey/p/solution-luogu-1141.html
版权:本文采用「署名-非商业性使用-相同方式共享 4.0 国际」知识共享许可协议进行许可。
觉得文章不错,点个关注呗!
原文地址:https://www.cnblogs.com/reqwey/p/solution-luogu-1141.html
- “AS3.0高级动画编程”学习:第二章转向行为(上)
- Linux下性能调试工具-top和sar运维笔记
- Apache+wsgi+flask部署
- “勒索病毒”到底会勒索啥,尽可以做到让全球对之恐惧无奈!
- 解决win10 关键错误开始菜单和cortana无法工作 的问题(转-真的成功了)
- “AS3.0高级动画编程”学习:第二章转向行为(下)
- windows系统中eclipse C开发环境的架设
- 5个酷毙的Python工具
- ”盒模型“之如何防止边框和内边距把元素撑开
- excel中的不同类型图表叠加
- 这几天遇到的关于IE6/sql2008/win2003的奇怪bug
- 基于Web的工作流管理系统的设计与实现
- 这是对position讲解最通俗易懂的版本了。
- 你到底该如何看待比特币?
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法