状态压缩DP+枚举子集 BZOJ2073 过桥

本文介绍了一种使用状态压缩动态规划算法解决特定过桥问题的方法。问题涉及一群人在受重量限制的情况下尽可能快地过桥,需要找到最优的分组方式以减少总过桥时间。文章详细解释了如何通过二进制位表示分组,并给出了具体的实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天更状态压缩的一支,要枚举子集的DP算法;

题目:
 一只队伍在爬山时碰到了雪崩 , 他们在逃跑时遇到了一座桥 , 他们要尽快的过桥 . 桥已经 很旧了 , 所以它不能承受太重的东西 . 任何时候队伍在桥上的人都不能超过一定的限 制 . 所以这只队伍过桥时只能分批过 , 当一组全部过去时 , 下一组才能接着过 . 队伍里每个 人过桥都需要特定的时间 , 当一批队员过桥时时间应该算走得最慢的那一个 , 每个人也有 特定的重量 , 我们想知道如何分批过桥能使总时间最少 .
 Input
 第一行两个数 : w —— 桥能承受的最大重量 (100 <= w <= 400) 和 n —— 队员总数 (1 <= n <= 16). 接下来 n 行每行两个数分别表示 : t – 该队员过桥所需时间 (1 <= t <= 50) 和 w – 该队员的重量 (10 <= w <= 100).
 Output
 输出一个数表示最少的过桥时间 .

样例输入 :

100 3

24 60

10 40

18 50

样例输出    42


这道题目要先枚举出人员的分组数,再对每组进行动态规划算出最短时间。分组用16位(最多16个队员)的2进制来表示,比如说:10110  5个位,3名队员编号分别为2,3,5 为一组,n位上的0表示编号为n的队员不在这组,1则表示在本组;

重要的是如何列举出所有的分组情况://给出代码

for(int i = 1; i < p[n]; ++ i)
for(int j = i; j <= n ; ++ j)
if(i & p[j-1])
ztime[i] = max(time[j],ztime[i]), zweight[i] += w[j];

设p[n]为2的n次方,即枚举的方案组数;ztime[]是这个队伍中走最长时间的队员的时间;zweight[]是该队伍总的重量;

i & p[j-1] 即在判断第j号队员是否在i表示的组成员中,非0—在 ,0—不在。

之后再对分组进行动态规划:

for(int i = 1; i < p[n]; ++ i)
   for(int j=1,u=0;j && u!=j; j = i & (j-1) )
      if(zweight[j] <= W)
        u=i^j,dp[i] = min(dp[i], dp[u]+ztime[j]);

这里说一下:1) i&j-1 表示除了该队伍最后一个队员的新的队伍(拆分枚举)

                     2)min(dp[i],dp[u]+ztime[j])表示没组这支队伍i方案的时间,和组了这支队伍的时间(这支队伍的时间+剩下人的时间)

                       3)已经给出的是优化型,下面给出原型代码

for(int i = 1; i < p[n]; ++ i)
   for(int j=1;j ; j = i & (j-1) )
      if(zweight[j] <= W)
        dp[i] = min(dp[i], dp[i^j]+ztime[j]);

大家可以思考下有什么区别(提示:过滤掉重复的队伍)

下面给出代码:

分割线


#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1 << 16;//2的16次方,最大方案 
int dp[MAXN],zweight[MAXN],ztime[MAXN];//dp数组,方案重量,方案时间 
int main(){
	int W,n; 
	cin>>W>>n;
	int p[n+1], t[n+1], w[n+1];
	memset(p,1,sizeof(p));//p的初始化 
	for(int i = 1;i <= n; ++ i)
	cin>>t[i]>>w[i],p[i] = 1 << i;//i队员的编号位置 
	for(int i = 1; i < p[n]; ++ i)//总方案 
	   for(int j = 1; j <= n; ++ j)//总人数 
	      if(i & p[j-1])//i队员在j队伍中 
	        ztime[i] = max(t[j], ztime[i]),zweight[i] += w[j];//队伍的时间和重量 
    memset(dp,0x3f,sizeof(dp)); dp[0] = 0;//初始化dp 
	for(int i = 1; i < p[n]; ++ i)
	   for(int j = i, u = 0; j && (j != u); j = i&(j-1))//上面详细说了,这里略过 
	      if(zweight[j] <= W)
	        u=i^j,dp[i] = min(dp[i], dp[u]+ztime[j]);
	cout<<dp[p[n]-1]<<endl;
	return 0;
}

最后:

programming is the most fun you can have with your clothes on.

### 如何用编程方法生成集合的所有子集 #### 时间复杂度与基本原理 枚举子集的核心在于理解其时间复杂度以及背后的逻辑。对于一个大小为 \( n \) 的集合,其所有可能的子集数量为 \( 2^n \),这是因为每个元素都有两种状态:要么属于某个子集,要么不属于该子集[^1]。 这种指数级的时间复杂度意味着,在实际应用中,通常只适合处理较小规模的数据集(例如 \( n \leq 30 \))。为了高效地生成这些子集,可以通过位运算或者递归来实现。 --- #### 方法一:基于位运算的方法 通过将整数范围内的每一个数字视为一种掩码来表示不同的子集组合。具体来说: - 假设集合中的元素编号从 0 到 \( n-1 \)。 - 对于任意一个介于 \( 0 \) 和 \( 2^n - 1 \) 范围内的整数 \( i \),将其转化为二进制形式,则第 \( j \)-bit 表示是否选取原集合中的第 \( j \) 号元素。 以下是 Python 实现代码: ```python def generate_subsets_bitmask(s): n = len(s) subsets = [] for mask in range(1 << n): # 遍历从 0 到 (2^n - 1) subset = [s[j] for j in range(n) if (mask & (1 << j))] subsets.append(subset) return subsets # 测试例子 example_set = ['a', 'b', 'c'] result = generate_subsets_bitmask(example_set) print(result) ``` 上述程序利用了按位操作符 `&` 来判断某一位是否被设置为 1,从而决定当前元素是否加入到对应的子集中[^3]。 --- #### 方法二:基于递归的方式 另一种常见的做法是采用分治的思想,即每次考虑下一个未决策的元素是否有资格进入当前构建过程中的部分解里去形成新的候选方案之一;最终当遍历完成整个数组之后便能够得到完整的幂集结构。 下面是 C++ 版本的一个简单示范: ```cpp #include <iostream> #include <vector> using namespace std; void findSubsets(int index, vector<int> currentSet, const vector<int>& originalSet, vector<vector<int>>& allSubsets){ if(index == originalSet.size()){ allSubsets.push_back(currentSet); return; } // 不选择当前位置上的元素 findSubsets(index + 1, currentSet, originalSet, allSubsets); // 选择当前位置上的元素 currentSet.push_back(originalSet[index]); findSubsets(index + 1, currentSet, originalSet, allSubsets); } int main(){ vector<int> set = {1, 2, 3}; vector<vector<int>> result; findSubsets(0, {}, set, result); cout << "All Subsets:" << endl; for(auto s : result){ cout << "{ "; for(auto elem : s){ cout << elem << " "; } cout << "}" << endl; } return 0; } ``` 此版本展示了如何通过函数调用来模拟树形搜索路径,并逐步累积每条分支的结果直到叶节点处收集完毕为止[^2]。 --- #### 复杂度分析 无论采取哪种方式实现,由于都需要访问全部潜在可能性因此总体计算量必然达到 \( O(2^n * k) \),其中 \( k \) 是平均单个输出所需的操作次数(比如拷贝列表等额外开销),而空间消耗则取决于所使用的数据存储策略及其最大深度限制条件下的栈帧需求情况。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值