7行代码解决P1441砝码称重(附优化过程)
先贴上最终代码感受一下:
#include <bits/stdc++.h>
using namespace std;
int i, N, M, wi[21], res = 0;
int main()
{
cin >> N >> M;
for(i = 1; i <= N; i++)cin >> wi[i];
int S = (1 << (N - M)) - 1; //(1)
while (S < 1 << N) //(2)
{
bitset<2010> f; //(3)
for(i = 1, f[0] = 1; i <= N; i++)if(S & 1 << (i - 1)) f |= f << wi[i]; //(4)
res = max(res, int(f.count())); //(5)
int x = S & -S, y = S + x; //(6)
S = ((S & ~y) / x >> 1) | y; //(7)
}
cout << res - 1;
return 0;
}
它的核心代码只有7行!!(1)~(7)
这段代码在此题最优解排行榜排名第四!!!(附截图,截图时间2021年1月24日,12:03:02)
解决思路为枚举大小为k的子集+bitset优化DP
下面是我的优化过程
题干
P1441 砝码称重
题目描述
现有n个砝码,重量分别为 ai,在去掉 m 个砝码后,问最多能称量出多少不同的重量(不包括 0)。
请注意,砝码只能放在其中一边。
输入格式
第 1 行为有两个整数 n 和 m,用空格分隔。
第 2 行有 n 个正整数a1,a2,a3,…,an,表示每个砝码的重量。
输出格式
仅包括 1 个整数,为最多能称量出的重量数量。
输入输出样例
输入 #1
3 1
1 2 2
输出 #1
3
说明/提示
【样例说明】
在去掉一个重量为 2 的砝码后,能称量出 1, 2, 3 共 3 种重量。
【数据规模】
对于 20% 的数据,m = 0 m = 0。
对于 50% 的数据,m ≤ 1。
对于 50% 的数据,n ≤ 10。
对于 100% 的数据,n ≤ 20, m ≤ 4,m < n,ai ≤ 100。
题目分析
我们不难看出,解决此题要解决两个子问题:
找到n个砝码去掉m个砝码的不同方案。
对每个方案再求出不同重量的方案数(使用当前方案的砝码可以凑出多少个不同的重量)。
解法一:搜索+DP
用搜索枚举出n个砝码去掉m个砝码的方案(状态),再用DP求不同重量的方案数。
#include <bits/stdc++.h>
#define maxn 2010
using namespace std;
int N, M;//有N个砝码,去掉M个砝码
int wi[maxn];//每个砝码的重量
bool vis[maxn];//每个砝码的状态:false被移除,true未被移除(存在)
int res = 0;//不同重量的最大方案数
bool f[maxn];//dp数组,f[n]表示是否存在一个重量为n的方案
//动态规划求不同重量的方案数
void dp()
{
memset(f, false, sizeof(f));//重置f数组
int ans = 0;//不同重量的方案数
f[0] = true; //存在重量为0的方案(不同的重量不包含0,所以不计入方案数)
int max_w = 0; //存在方案的最大重量
for(int i = 1; i <= N; i++) //选第i个砝码
{
if(!vis[i])continue;//如果第i个砝码被移除就跳过
for(int j = max_w; j >= 0; j--)
{
//如果存在重量为j的方案,那么j+wi[i]的方案也一定存在
if(f[j] && !f[j + wi[i]])
{
f[j + wi[i]] = true;
ans++;//方案数加一
}
}
max_w+=wi[i];//更新最大重量
}
res = max(res, ans);//维护最大方案数
}
//利用搜索算法找N个砝码去掉M个砝码的方案(维护vis数组)
void dfs(int cur, int del) //cur 当前该维护哪个砝码的状态;del 已经移除了几个砝码
{
//搜索剪枝
if(del > M)return; //移除的砝码数大于M
//搜索边界
if(cur == N + 1) //一共有N个砝码,处理第N+1个砝码时到达搜索边界
{
if(del == M) //如果恰好删除了M个,即找到N个砝码去掉M个砝码的一个方案
{
dp();//维护这个方案下,不同重量的最大方案数(维护res)
}
return;
}
//分支1:不移除当前砝码
dfs(cur + 1, del); //直接维护第cur+1个砝码,因为没有移除操作,del不变
//分支2:移除当前砝码
vis[cur] = false; //移除当前砝码
dfs(cur + 1, del + 1);
vis[cur] = true; //回溯时将移除的砝码重新加回去
}
int main()
{
cin >> N >> M;
for(int i = 1; i <= N; i++)cin >> wi[i];//输入每个砝码重量
memset(vis, true, sizeof(vis));//初始化vis数组
dfs(1, 0);//初始搜索的状态
cout << res;
return 0;
}
解法二:子集枚举+DP
用“集合”的子集枚举方法枚举状态,然后用popcount判断是否每个状态是否满足条件,对满足条件的状态用DP求不同重量的方案数。
#include <bits/stdc++.h>
#define maxn 2010
using namespace std;
int N, M;//有N个砝码,去掉M个砝码
int wi[maxn];//每个砝码的重量
int res = 0;//不同重量的最大方案数
bool f[maxn];//dp数组,f[n]表示是否存在一个重量为n的方案
//动态规划求不同重量的方案数
void dp(int S)//“集合”S
{
memset(f, false, sizeof(f));//重置f数组
int ans = 0;//不同重量的方案数
f[0] = true; //存在重量为0的方案(不同的重量不包含0,所以不计入方案数)
int max_w = 0; //存在方案的最大重量
for(int i = 1; i <= N; i++) //选第i个砝码
{
if(S & 1 << (i - 1))//利用位运算判断第i个元素(砝码)是否在“集合”内
{
for(int j = max_w; j >= 0; j--)
{
//如果存在重量为j的方案,那么j+wi[i]的方案也一定存在
if(f[j] && !f[j + wi[i]])
{
f[j + wi[i]] = true;
ans++;//方案数加一
}
}
max_w += wi[i]; //更新最大重量
}
}
res = max(res, ans);//维护最大方案数
}
//统计二进制中1的个数
int popcount(int S)//“集合”S
{
int ans = 0;//ans为S的二进制中,二进制位为1的个数
while (S)
{
if (S & 1)
{
ans++;
}
S >>= 1;
}
return ans;
}
void subset()//“集合”子集枚举
{
//(1 << N )- 1的二进制有N位且都为1
//S的每个二进制位代表一个元素,即代表一个砝码
//二进制位为1表示该元素在“集合”内
//二进制位为0表示该元素不在“集合”内
for(int S = 0; S <= (1 << N ) - 1; S++) //N个元素的“集合”的子集枚举
{
if(popcount(S) == N - M)//当“集合”S中有N-M个元素(S的二进制中有N-M个1)
{
dp(S);//根据“集合”S的状态维护最大方案数
}
}
}
int main()
{
cin >> N >> M;
for(int i = 1; i <= N; i++)cin >> wi[i];//输入每个砝码重量
subset();
cout << res;
return 0;
}
解法二优化:大小为k的子集枚举+DP
优化解法二的子集枚举部分,直接枚举大小为k的子集,再对每个子集DP求不同重量的方案数。
直接套用枚举大小为k的子集的模板代码:
int comb = (1 << k) - 1;
while (comb < 1 << n) {
//进行针对组合的处理
int x = comb & -comb, y = comb + x;
comb = ((comb&~y) / x >> 1) | y;
}
套用后对应的代码:
int S = (1 << (N - M)) - 1;
while (S < 1 << N)
{
dp(S);//根据“集合”S的状态维护最大方案数
int x = S & -S, y = S + x;
S = ((S & ~y) / x >> 1) | y;
}
关于如何枚举大小为k的子集,在我的另一篇博客有详细的讲解,在这里就不详细展开了。
有兴趣的小伙伴可以看一下我的这篇博客:关于“枚举{0,1,...,n-1}所包含的所有大小为k的子集”的理解
完整代码:
#include <bits/stdc++.h>
#define maxn 2010
using namespace std;
int N, M;//有N个砝码,去掉M个砝码
int wi[maxn];//每个砝码的重量
int res = 0;//不同重量的最大方案数
bool f[maxn];//dp数组,f[n]表示是否存在一个重量为n的方案
//动态规划求不同重量的方案数
void dp(int S)//“集合”S
{
memset(f, false, sizeof(f));//重置f数组
int ans = 0;//不同重量的方案数
f[0] = true; //存在重量为0的方案(不同的重量不包含0,所以不计入方案数)
int max_w = 0; //存在方案的最大重量
for(int i = 1; i <= N; i++) //选第i个砝码
{
if(S & 1 << (i - 1))//利用位运算判断第i个元素(砝码)是否在“集合”内
{
for(int j = max_w; j >= 0; j--)
{
//如果存在重量为j的方案,那么j+wi[i]的方案也一定存在
if(f[j] && !f[j + wi[i]])
{
f[j + wi[i]] = true;
ans++;//方案数加一
}
}
max_w += wi[i]; //更新最大重量
}
}
res = max(res, ans);//维护最大方案数
}
void k_subset()//“集合”大小为k的子集枚举
{
//对“集合”S进行大小为N-M的子集枚举
int S = (1 << (N - M)) - 1;
while (S < 1 << N)
{
dp(S);//根据“集合”S的状态维护最大方案数
int x = S & -S, y = S + x;
S = ((S & ~y) / x >> 1) | y;
}
}
int main()
{
cin >> N >> M;
for(int i = 1; i <= N; i++)cin >> wi[i];//输入每个砝码重量
k_subset();
cout << res;
return 0;
}
继续优化解法二:大小为k的子集枚举+bitset优化DP
利用bitset优化DP部分,其他部分不变。
#include <bits/stdc++.h>
#define maxn 2010
using namespace std;
int N, M;//有N个砝码,去掉M个砝码
int wi[maxn];//每个砝码的重量
int res = 0;//不同重量的最大方案数
//动态规划求不同重量的方案数
void dp(int S)//“集合”S
{
int ans;//不同重量的方案数
bitset<maxn> f;
f[0] = 1;
for(int i = 1; i <= N; i++) //选第i个砝码
{
if(S & 1 << (i - 1))//利用位运算判断第i个元素(砝码)是否在“集合”内
{
f |= f << wi[i];//bitset优化的核心代码
}
}
ans = f.count();
res = max(res, ans);//维护最大方案数
}
void k_subset()//“集合”大小为k的子集枚举
{
//对“集合”S进行大小为N-M的子集枚举
int S = (1 << (N - M)) - 1;
while (S < 1 << N)
{
dp(S);//根据“集合”S的状态维护最大方案数
int x = S & -S, y = S + x;
S = ((S & ~y) / x >> 1) | y;
}
}
int main()
{
cin >> N >> M;
for(int i = 1; i <= N; i++)cin >> wi[i];//输入每个砝码重量
k_subset();
cout << res - 1;//重量为0不作为一个方案,所以要减一
return 0;
}
最终代码
去掉注释再稍微调整一下结构就得到了最终代码:
#include <bits/stdc++.h>
using namespace std;
int i, N, M, wi[21], res = 0;
int main()
{
cin >> N >> M;
for(i = 1; i <= N; i++)cin >> wi[i];
int S = (1 << (N - M)) - 1;
while (S < 1 << N)
{
bitset<2010> f;
for(i = 1, f[0] = 1; i <= N; i++)if(S & 1 << (i - 1)) f |= f << wi[i];
res = max(res, int(f.count()));
int x = S & -S, y = S + x;
S = ((S & ~y) / x >> 1) | y;
}
cout << res - 1;
return 0;
}
说实话,能优化到这个地步,我自己都被惊艳到了
7行代码解决P1441砝码称重(附优化过程)的更多相关文章
- 洛谷P1441 砝码称重
P1441 砝码称重 题目描述 现有n个砝码,重量分别为a1,a2,a3,……,an,在去掉m个砝码后,问最多能称量出多少不同的重量(不包括0). 输入输出格式 输入格式: 输入文件weight.in ...
- 洛谷P1441 砝码称重(搜索,dfs+bitset优化)
洛谷P1441 砝码称重 \(n\) 的范围为 \(n \le 20\) ,\(m\) 的范围为 \(m \le 4\) . 暴力遍历每一种砝码去除情况,共有 \(n^m\) 种情况. 对于剩余砝码求 ...
- 洛谷P1441 砝码称重(搜索,dfs+dp)
洛谷P1441 砝码称重 \(n\) 的范围为 \(n \le 20\) ,\(m\) 的范围为 \(m \le 4\) . 暴力遍历每一种砝码去除情况,共有 \(n^m\) 种情况. 对于剩余砝码求 ...
- 洛谷 P1441 砝码称重
题目描述 现有n个砝码,重量分别为a1,a2,a3,……,an,在去掉m个砝码后,问最多能称量出多少不同的重量(不包括0). 输入输出格式 输入格式: 输入文件weight.in的第1行为有两个整数n ...
- P1441 砝码称重 DFS回溯+DP
题目描述 现有n个砝码,重量分别为a1,a2,a3,……,an,在去掉m个砝码后,问最多能称量出多少不同的重量(不包括0). 请注意,砝码只能放在其中一边. 输入输出格式 输入格式: 输入文件weig ...
- P1441 砝码称重
题目描述 现有n个砝码,重量分别为a1,a2,a3,……,an,在去掉m个砝码后,问最多能称量出多少不同的重量(不包括0). 输入输出格式 输入格式: 输入文件weight.in的第1行为有两个整数n ...
- P1441 砝码称重(搜索+队列dp)
题目链接:传送门 题目大意: 给你n个砝码ai,从中去掉m个后求最多的砝码可表示的重量. n≤20,m≤4,m<n,ai≤100. 思路: 用dfs搜掉m个砝码,然后用队列dp跑出答案,维护答案 ...
- [Luogu] P1441 砝码称重
题目描述 现有n个砝码,重量分别为a1,a2,a3,……,an,在去掉m个砝码后,问最多能称量出多少不同的重量(不包括0). 题目分析 因为读错题WAWA大哭. 先dfs枚举选的砝码,满足条件时进行d ...
- 洛谷P1441 砝码称重 枚举 + 01背包
显然,n<=20, m<=4 的数据范围一眼爆搜. 直接搜索一下不用哪4个砝码,再做一遍01背包即可. 可能是本人太菜鸡,01背包部分调了半天QAQ-- #include<cstdi ...
随机推荐
- [OI笔记]杂题整理1(基础篇~)
算是开学第四周啦,之前的三周大概过了一遍基础图论和数学相关的内容.这篇随笔打算口胡一些近期做感觉比较好的数学相关的题目 因为这段时间主要是看紫书学的,所以其实会有些出自UVA的例题,如果需要题目但是觉 ...
- 一道java算法题
12个人围成一圈,序号依次从1至12,从序号1开始顺时针依次数,数到7的人退出,下一个再依次从1开始数,求留下来的最后一个人的原始序号. public static void joseph(int[] ...
- 详解Js加法运算符
简介 JavaScript是一门了不起的语言.我喜欢它的灵活性:只需以你喜欢的方式做事:更改变量类型,动态的向对象添加方法或属性,对不同的变量类型使用运算符等等. 然而动态是要付出代价的,开发人员需要 ...
- 相对于Statement,PreparedStatement的优点是什么?
优点: 1.PreparedStatement有助于防止SQL注入,因为它会自动对特殊字符转义. 2.PreparedStatement可以用来进行动态查询. 3.PreparedStatement执 ...
- java零基础之--【Lombok】简化类设计神器
I1. 在类设计中我们必不可少的要进行属性定义,构造方法,setter/getter方法,toString方法定义,如果在设计项目中属性过多则会影响类的阅读性. Lombok作为第三方插件,很好的解决 ...
- 对象、对象监视器、同步队列、执行线程关系(synchronized的实现细节或原理)
synchronized在使用的时候底层细节你了解吗,相信很多同学对细节很少关注:比如竞争失败了的线程怎么安置,每个对象的监视器,线程执行synchronized时,其实是获取对象的监视器才能进入同步 ...
- b站视频下载技术分享
最近无聊分析了一下b站的视频流协议,简单分享下爬取的流程. 首先先要找到视频对应的aid和cid,aid就相当于av号,而av号对应网页下的每一个视频都有对应的cid,普通视频就是分p,番剧就是集数, ...
- CSS-position 属性&元素脱离文档流引发父级边框塌陷问题
CSS-position 属性 CSS 定位机制 CSS 有三种基本的定位机制:普通流.浮动(float)和绝对定位(position). 除非专门指定,否则所有框都在普通流中定位.也就是说,普通流中 ...
- ECMAScript概述及浅谈const,let与块级作用域
ECMAScript可以看作javascript的标准规范,实际上javascript是ECMAScript的一门脚本语言,ECMAScript只提供了最基本的语言JavaScript对ECMAScr ...
- flume的一些使用
一.第一层采集通道的编写 1.第一层采集脚本Source的选择①Source: 数据源在日志文件中! 读取日志中的数据,可以使用以下Source ExecSource: 可以执行一个linux命令,例 ...