P1164 小A点菜(两种解法dfs+剪枝/dp)

题目

dfs+剪枝

#include 

using namespace std;
bool visit[1005]={false};
int ans=0;
int n,mon;
int w[1005];///菜品价格
int cnt1=0,cnt=0;
///01背包用搜索解决,每个东西只能选一次,注意index的使用!!!
void dfs(int money,int index)
{
    if(money==mon){ans++; return;}
    if(money>mon) return;
    ///从第index道菜开始选,例如有1 2 3 4 4道菜,1与2,3,4排列组合了,之后就不选1了,2与3,4排列组合
    for(int i=index;i<n;i++)
    {

        //if(cnt1==n){cnt++;n--;cnt1=0;}cnt1++;
        if(visit[i]==false)
        {
            visit[i]=true;
            dfs(money+w[i],i);
            visit[i]=false;
        }
        //cout<
    }

}
int main()
{
    cin>>n>>mon;
    for(int i=0;i<n;i++) cin>>w[i];
    dfs(0,0);
    cout<<ans;
    return 0;
}

dp
其状态转移方程如下:

(1)if(j==第i道菜的价格)f[i][j]=f[i-1][j]+1;

(2)if(j>第i道菜的价格) f[i][j]=f[i-1][j]+f[i-1][j-第i道菜的价格];

(3)if(j<第i道菜的价格) f[i][j]=f[i-1][j];

说的简单一些,这三个方程,每一个都是在吃与不吃之间抉择。若钱充足,办法总数就等于吃这道菜的办法数与不吃这道菜的办法数之和;若不充足,办法总数就只能承袭吃前i-1道菜的办法总数。依次递推,在最后,我们只要输出f[n][m]的值即可。

for(int i=0;i<n;i++)
      for(int j=1;j<=m;j++)
      {
          if(j==a[i])f[i][j]=f[i-1][j]+1;
          if(j>a[i]) f[i][j]=f[i-1][j]+f[i-1][j-a[i]];
          if(j<a[i]) f[i][j]=f[i-1][j];
      }

优化

过程2
接下来就是如何把数组从2维降到1维了
我们先来考虑这样的一个问题,我们要输出a*b的答案,如果硬要用数组做的话可以用一个for循坏从1到a然后另s[i]=s[i-1]+b;最后输出s[a]。

但是通常情况下,我们都是直接用s+=b来实现这个问题,这就是一种数组降维。为什么可以降下来,因为这个问题我们每一次只会使用到i-1,而i-1就是上一次做完留下来的值,所以根本不需要用数组来做这个问题。

然后回到原问题,我们发现f数组的第一维每次也只是使用到了i-1,所以说我们可以给f数组降维。

但是这个时候需要注意一个问题,我们的第二层for循环不能正着跑了,为什么?我们可以观察一下下面两幅图片
P1164 小A点菜(两种解法dfs+剪枝/dp)_第1张图片
P1164 小A点菜(两种解法dfs+剪枝/dp)_第2张图片

第一个图片表示倒着跑,这个时候遍历到的当前的j就是上一次的j

而第二个图片,遍历到的j是刚刚更新了一次的j
(以上优化转载洛谷 Dream_zhc)

int f[10009];
int main()
{
    f[0]=1;
    int n;cin>>n;
    int m;cin>>m;
    int a[n+5];
    for(int i=0;i<n;i++) cin>>a[i];
    for(int i=0;i<n;i++)
    {
        for(int j=m;j>=a[i];j--)
        {
            f[j]+=f[j-a[i]];
        }
    }
    cout<<f[m];
}

你可能感兴趣的:(dfs,动态规划(dp))