位运算 糖果

位掩码

左移运算<<  箭头的符号表示 向左移动并且添0。

口味编号的输入

合法状态 :循环的条件为i<(1<<m)


 较大的整数值不能用memset来设置

 

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const ll inf=2e18;
const int N=1e2+9;
ll a[N],dp[1<<21];
int n,m,k;

int main()
{
  cin>>n>>m>>k;
  for(int i=1;i<=n;i++)//每一包糖果
  {
    for(int j=1;j<=k;j++)//一包糖果的口味集合是每颗糖组成的
    {
      int x;cin>>x;
      a[i]|=1<<(x-1);
    }
  }
  for (int sta = 1; sta < (1 << m); ++sta) dp[sta]=inf; 
  for(int sta=0;sta<(1<<m);sta++)
  {//对于所有的口味
    for(int i=1;i<=n;i++)//对于每一包糖果
    {
      dp[sta|a[i]]=min(dp[sta|a[i]],dp[sta]+1);
    }
  }
  ll ans=dp[(1<<m)-1];
  cout<<(ans==inf?-1:ans);
	return 0;
} 

 


 位运算中注意编号和位的关系

第一位(从右往左)编号为0;第二编号为1;第x位编号为x-1

1<<0结果为000001 第1位编号为0 ;1<<1结果为0000010第二位编号为1

与运算,只有都为1的时候结果为1


算法讲解003【入门】二进制和位运算_哔哩哔哩_bilibili

负数和二进制相互转化:

负数转化二进制:负数先看成正数,然后-1,然后每一位取反

相反数:取反之后+1得到相反数

对于非负数左移和右移的效果就是添加几个0;

如1<<3 = 1000



 状态转移方程的解释

 


其他方案对最终方案的辅助作用:

 

//末尾有状态解释
// dp[sta] 表示在状态 sta 下,小明已经品尝到的糖果口味的集合的最小购买数量
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 105; 
const ll inf = 2e18; //无穷大
ll a[N], dp[1<<21]; // 每包糖果的口味位掩码
//通过位掩码来表示糖果被品尝的状态,
// 这个题目种只有20种糖果,初始化有21位,这样能够通过每一位是否为1来表示物品是否被选择
// 即糖果是否被品尝到
int main() {
    int n, m, k; // 读取糖果包数量n,口味种类m,每包糖果数量k
    cin >> n >> m >> k;
    for (int i = 1; i <= n; ++i) { // 遍历每包糖果
        for (int j = 1; j <= k; ++j) { // 遍历每包糖0果中的每种口味
            int x; // 读取第j种口味的编号
            cin >> x;
            a[i] |= (1 << (x - 1)); /*使用位运算将第j种口味添加到第i包糖果的位掩码中
            左移就是在数的右边添加几个零
            这样原来的数就会在第四位上,但是为了表示编号,所以要-1
            如 a[i]<<3表示第3种口味输入了,但是他会在第四位上
            1000,想让他成为100在第三位上,就要写成a[i]<<3-1;
            然后再取或运算添加到目标的数上
            */
        }
    }

    for (int sta = 1; sta < (1 << m); ++sta) dp[sta]=inf; // 初始化dp数组,除了dp[0]外,其他状态的初始值为无穷大
    
    for (int sta=0;sta<(1<<m);sta++) {
    //为什么这里的初始条件设置为<(1<<m)?
        for (int i = 1; i <= n; ++i) { // 遍历每包糖果
            dp[sta|a[i]]=min(dp[sta|a[i]],dp[sta]+1); // 状态转移:如果通过购买第i包糖果可以从状态sta转移到状态sta|a[i],则更新dp[sta|a[i]]
        // 在购买第i包糖果得到的新组合可以由旧状态+1得到,
        //也可能在购买别的包(比如第j包)的时候就已经得到,所以要比较这两种情况的最小购买次数
        // 作为新方案的购买次数
        }
        // dp[sta|a[i]]通过购买糖果之后小明可以尝到的口味合集
        //在当前状态 sta 下,
        // 小明购买第 i 包糖果后,品尝到的新口味的集合的最小购买数量加1。
    }
    // 最后只是需要全部吃到的方案,为什么还要求其他品尝方案的最小购买量?
    // 对目标结果有什么影响?
    ll ans = dp[(1 << m) - 1]; 
    cout << (ans == inf ? -1 : ans) << '\n';
    return 0;
    // 最后检查是否存在全品尝完的情况
    // 即全部买完之后能够品尝到全部种类的糖果

}

### 蓝桥杯 Python 分糖果 问题 解题思路 分糖果问题是典型的组合计数问题,通常涉及分配策略以及约束条件下的穷举计算。以下是针对该问题的详细分析和解决方案。 #### 题目解析 假设题目要求将一定数量的糖果分配给若干孩子,每个孩子的糖果数量需满足特定范围(如最少2颗最多5颗)。目标是统计所有可能的有效分配方案总数。此问题可以通过深度优先搜索 (DFS) 或动态规划解决。 #### 示例代码实现 以下是一个基于 DFS 的 Python 实现: ```python def dfs(current_child, remaining_a, remaining_b): global count # 如果已经处理到最后一个孩子,则判断剩余糖果是否刚好被分配完毕 if current_child >= total_children: if remaining_a == 0 and remaining_b == 0: count += 1 return # 尝试为当前孩子分配不同数量的糖果A和B for i in range(remaining_a + 1): # 糖果A的数量 for j in range(remaining_b + 1): # 糖果B的数量 if 2 <= i + j <= 5: # 每个孩子至少获得2颗糖,至多获得5颗糖 dfs(current_child + 1, remaining_a - i, remaining_b - j) # 初始化全局变量并调用函数 total_children = 8 # 假设有8个孩子 count = 0 dfs(1, 9, 16) # 初始状态:第一个孩子,还有9颗糖果A和16颗糖果B未分配 print(count) ``` 上述代码的核心逻辑在于通过递归枚举每种可能的孩子糖果分配情况,并利用边界条件过滤非法解。最终累加符合条件的结果数目[^4]。 #### 关键点说明 1. **递归终止条件** 当 `current_child` 达到总人数时停止递归;此时如果恰好无剩余糖果则计入有效方案。 2. **剪枝优化** 对于超出合法区间 `[2, 5]` 的糖果分配直接忽略,减少不必要的分支运算。 3. **复杂度考量** 此方法的时间复杂度较高,在数据规模较大时可能导致性能瓶颈。因此实际应用中可考虑引入记忆化存储或转换成动态规划形式进一步提升效率。 #### 动态规划替代方案概述 虽然此处采用的是暴力搜索方式,但对于更大规模输入建议改用 DP 方法求解。定义三维数组 dp[i][j][k] 表示前i名学生已安排好且分别剩下j,k两类糖果的情况下有多少种可行配置路径。转移方程如下所示: \[ \text{dp}[i][j][k]=\sum_{p,q}\text{dp}[i−1][j−p][k−q], \quad s.t.\ p+q∈[2,5]\] 其中 \(p\) 和 \(q\) 分别代表第\(i\)位同学所拿取的第一类与第二类糖果量。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值