穷举法解倒酒问题

博客围绕将16两酒平均分给4人喝的问题展开,作者借助程序用穷举法求解。定义了容器类,包括瓶子、杯子和人,用哈希值表示酒的分布状态,递归时记录状态避免走回头路,最终输出最少步骤的方案。

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

mop上见到的问题:有两个容量为8两的瓶子,各装满酒。有一只杯子。要把这16两酒平均分给4个人喝。问怎么样倒酒。(后附能在XX时间内解出的的智商有N高云云。。。)

俺智商较低,借助程序求解。

思路:穷举法#$#~#@!

容器有三种。瓶子和杯子有最大容量,并且可以倒出也可以倒入。“人”没有最大容量,可以倒出不能倒入。人容纳的酒量大于4的状态就算game over。

16两酒的分布状态可用一个唯一的哈希值表示。使用情况是瓶子用4位,杯子用3位(2位都可以了),人用3位。递归时把当前状态放进列表中。每种分布状态在列表中可能出现的值有:已访问,死亡,成功。已访问的结点在下一层递归将被忽略。若下一层的所有可能方案均返回死亡,则此层也被置为死亡。这样就确保了不会走回头路,因此此算法是非常快的。

最后输出的是最少步骤的方案。

// BottleGame.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "stdio.h"
#include "conio.h"

#include <map>
#include <vector>

using namespace std;

class CContainer
{
protected:
 int m_nCapability;
 int m_nWeight;
 char* m_pszName;
public:
 CContainer(char *name, int C, int W)
 {
  m_nCapability = C;
  m_nWeight = W;
  m_pszName = name;
 };
 void SetWeight(int w) { m_nWeight = w; }
 int GetWeight(void) { return m_nWeight; }
 int Pour(CContainer* pReceiver)
 {
  int n = GetWeight();
  if (n > pReceiver->GetCapability() - pReceiver->GetWeight())
    n = pReceiver->GetCapability() - pReceiver->GetWeight();
  pReceiver->SetWeight(pReceiver->GetWeight() + n);
  SetWeight(GetWeight() - n);
  return n;
 };
 void UnPour(CContainer* pReceiver, int n)
 {
  pReceiver->SetWeight(pReceiver->GetWeight() - n);
  SetWeight(GetWeight() + n);
 };
 int GetCapability(void) { return m_nCapability; }
 char* GetName(void) { return m_pszName; }
public:
 virtual bool CanPour(void) = 0;
 virtual int GetBits(void) = 0;
 virtual bool Die(void) = 0;
};

class CBottle : public CContainer
{
public:
 CBottle(char *name) : CContainer(name, 8, 8)
 {
 }
 virtual bool CanPour(void) { return true; }
 virtual int GetBits(void) { return 4; }
 virtual bool Die(void) { return false; }
};

class CCup : public CContainer
{
public:
 CCup(char *name = "cup") : CContainer(name, 3, 0)
 {
 }
 virtual bool CanPour(void) { return true; }
 virtual int GetBits(void) { return 3; }
 virtual bool Die(void) { return false; }
};

class CMan : public CContainer
{
public:
 CMan(char *name) : CContainer(name, 100, 0)
 {
 }
 virtual bool CanPour(void) { return false; }
 virtual int GetBits(void) { return 3; }
 virtual bool Die(void) { return GetWeight() > 4; }
};

enum visited_status {
 none,
 visited,
 dead,
 success
};

typedef struct _tagMethod {
 CContainer* pPourer;
 CContainer* pReceiver;
 int n;
} Method;

map<int/*key*/, visited_status> PathArray;
vector<CContainer*> Objs;
Method Methods[100];
int min_steps = 1000;

int get_status_hash(void)
{
 int n = 0, nLastBits = 0;
 for ( vector<CContainer*>::iterator it = Objs.begin();
  it != Objs.end(); ++it )
 {
  n <<= nLastBits;
  n += (*it)->GetWeight();
  nLastBits = (*it)->GetBits();
 }
 return n;
}

void dump_all(void)
{
 for ( vector<CContainer*>::iterator it = Objs.begin();
  it != Objs.end(); ++it )
 {
  printf("%8d", (*it)->GetWeight());
 }
 printf("/n");
}

enum MethodResult
{
 mrSucceed,
 mrDie,
 mrOverflow
};

enum MethodResult try_method(int level)
{
 CContainer *pPourer, *pReceiver;

 if (level >= min_steps) return mrOverflow; // exceeded

 if (level >= 300) return mrOverflow;

 int hash;
 map<int, visited_status>::iterator it;

 bool bAllDie = true;
 for ( vector<CContainer*>::iterator itPourer = Objs.begin();
  itPourer != Objs.end(); ++itPourer )
 {
  pPourer = *itPourer;
  if (!pPourer->CanPour()) continue;

  for ( vector<CContainer*>::iterator itReceiver = Objs.begin();
   itReceiver != Objs.end(); ++itReceiver )
  {
   pReceiver = *itReceiver;
   if (pReceiver == pPourer) continue;
   if (pReceiver->GetCapability() == pReceiver->GetWeight()) continue; // full

   int n = pPourer->Pour(pReceiver);

   if (pReceiver->Die())
   {
    pPourer->UnPour(pReceiver, n);
    continue;
   }

   Methods[level].pPourer = pPourer;
   Methods[level].pReceiver = pReceiver;
   Methods[level].n = n;

   hash = get_status_hash();
   it = PathArray.find(hash);
   if (it != PathArray.end())
   {
    if (it->second == success)
    {
     min_steps = level;
     pPourer->UnPour(pReceiver, n);
     return mrSucceed;
    }
    if (it->second == dead || it->second == visited)
    {
     pPourer->UnPour(pReceiver, n);
     continue; // die
    }
   }

//   dump_all();

   PathArray[hash] = visited;
   switch(try_method(level + 1))
   {
   case mrSucceed:
    if (PathArray[hash] == visited) PathArray.erase(hash);
    pPourer->UnPour(pReceiver, n);
    return mrSucceed;
   case mrDie:
    PathArray[hash] = dead;
    break;
   case mrOverflow:
    bAllDie = false;
    break;
   }
   if (PathArray[hash] == visited) PathArray.erase(hash);
   pPourer->UnPour(pReceiver, n);
  }
 }
 if (bAllDie)
 {
  hash = get_status_hash();
  PathArray[hash] = dead;
  return mrDie;
 }
 return mrOverflow;
}

int _tmain(int argc, _TCHAR* argv[])
{
 // init
 Objs.push_back(new CBottle("Bottle1"));
 Objs.push_back(new CBottle("Bottle2"));
 Objs.push_back(new CCup("Cup"));
 Objs.push_back(new CMan("A"));
 Objs.push_back(new CMan("B"));
 Objs.push_back(new CMan("C"));
 Objs.push_back(new CMan("D"));

 int hash = (((((4 << 3) + 4) << 3) + 4) << 3) + 4;
 PathArray[hash] = success;
 // try
 enum MethodResult Res = try_method(0);
 // print result
 if (Res == mrSucceed)
 {
  for ( int i = 0; i <= min_steps; i++ )
  {
   printf("%s -> %s : %d/n",
    Methods[i].pPourer->GetName(),
    Methods[i].pReceiver->GetName(),
    Methods[i].n
    );
  }
 }

 // clean up
 for ( vector<CContainer*>::iterator it = Objs.begin();
  it != Objs.end(); ++it ) delete (*it);

 printf("/npress any key.../n");
 getch();
 return 0;
}

<think>好的,我需要帮助用户用C语言通过穷举法决背包问题,并提供示例代码和释。首先,我得回忆一下背包问题的基本概念。0/1背包问题是指每个物品只能选择放入或不放入背包,目标是在不超过背包容量的情况下,使得物品的总价值最大。穷举法的思路是生成所有可能的物品组合,计算每个组合的重量和价值,然后找出满足条件的最优。 接下来,我需要考虑如何用C语言实现这一点。穷举法通常需要生成所有子集,这可以通过位运算来实现。例如,如果有3个物品,每个物品可以选或不选,总共有2^3=8种组合。每个组合可以用二进制数表示,每一位代表一个物品是否被选中。比如,二进制数101表示选择第1和第3个物品。 然后,我需要编写生成所有子集的代码。可以用一个循环,从0到2^n -1,每个数对应一个子集。对于每个数,检查每一位是否为1,以确定哪些物品被选中。同时,需要计算每个子集的总重量和总价值,如果总重量不超过背包容量,并且价值大于当前最大值,就更新最大值和最佳组合。 接下来,考虑代码的结构。首先定义物品的结构体,包含重量和价值。然后输入物品数量和背包容量,接着输入每个物品的信息。然后进行穷举循环,遍历所有可能的子集,计算每个子集的重量和价值,筛选出符合条件的并记录最大值。 需要注意的问题包括:如何处理大量物品时的效率问题,因为穷举法的时间复杂度是O(2^n),当n较大时不可行。但用户可能只是需要示例代码,所以暂时不考虑优化问题。此外,要确保代码正确生成所有子集,并且正确计算重量和价值。 然后,我需要测试代码是否正确。例如,假设有3个物品,重量分别为2、3、4,价值分别为3、4、5,背包容量为5。可能的组合有选物品1和2(总重量5,价值7),或者选物品1和3(重量6超过容量),或者选物品2(重量3,价值4)。最优应该是选物品1和2,总价值7。 最后,释代码的关键部分,比如如何用位运算生成子集,如何计算重量和价值,以及如何更新最优。同时需要提到穷举法的优缺点,适用于小规模问题。</think>### C语言穷举法决0/1背包问题示例 #### 算法思路 1. 生成所有物品组合的子集(共$2^n$种可能) 2. 计算每个子集的总重量和总价值 3. 筛选出总重量不超过背包容量的子集 4. 记录满足条件的最大总价值及其组合 #### 示例代码 ```c #include <stdio.h> #include <math.h> typedef struct { int weight; int value; } Item; int main() { int n, capacity; printf("输入物品数量: "); scanf("%d", &n); printf("输入背包容量: "); scanf("%d", &capacity); Item items[n]; for(int i=0; i<n; i++){ printf("输入第%d个物品的重量和价值: ", i+1); scanf("%d %d", &items[i].weight, &items[i].value); } int max_value = 0; int best_combination = 0; // 遍历所有可能的组合(2^n种) for(int i=0; i < (1<<n); i++){ // 位运算生成子集 int current_weight = 0; int current_value = 0; // 检查每个bit位 for(int j=0; j<n; j++){ if(i & (1<<j)){ // 判断第j位是否为1 current_weight += items[j].weight; current_value += items[j].value; } } // 更新最优 if(current_weight <= capacity && current_value > max_value){ max_value = current_value; best_combination = i; } } // 输出结果 printf("\n最大价值: %d\n组合方案: ", max_value); for(int j=0; j<n; j++){ if(best_combination & (1<<j)){ printf("%d ", j+1); } } return 0; } ``` #### 关键代码释 1. `1<<n`:通过位运算生成$2^n$个子集,每个bit位表示物品的选择状态[^3] 2. `i & (1<<j)`:检查第j个bit位是否为1,表示选择该物品 3. `current_weight`和`current_value`:实时计算当前组合的总重量和总价值 4. `best_combination`:用二进制位模式记录最优组合方案 #### 算法特点 - 时间复杂度:$O(2^n)$,指数级复杂度,适合小规模问题[^2] - 空间复杂度:$O(n)$,仅需存储物品信息 - 优势:保证找到全局最优 - 局限:物品数量超过20时计算时间急剧增加 #### 运行示例 ``` 输入物品数量: 3 输入背包容量: 5 输入第1个物品的重量和价值: 2 3 输入第2个物品的重量和价值: 3 4 输入第3个物品的重量和价值: 4 5 最大价值: 7 组合方案: 1 2 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值