【洛谷p1077】摆花

题外废话:

真的超级喜欢这道题


 

摆花【题目链接】

yy一提醒,我发现这道题和【洛谷p2089】 烤鸡有异曲同工之妙(数据更大了更容易TLE呢qwq)

SOLUTION1:(暴搜)

搜索:关于搜索就不用多介绍了吧,这里是用了dfs:dfs函数中有两个变量,rest和i分别表示还需要摆放多少盆花以及现在摆放到第几种花了,因为我们是从0开始枚举,因此如果某种花我们不选择,那就可以看做这种花选择了0盆;

几个返回条件:

1.rest==0时,记录ans++(ans是全局变量)return,不需要满足rest==0&&i=n+1(因为只要rest被枚举完了就可以了,后面的花不摆放就好啦)

2.i==n+1时,return;为什么是i=n+1呢,因为当i=n时,我们并没有枚举第n种花摆放的盆数,如果现在就返回,显然是会对结果造成很大的影响,进而影响整个结果;

3.rest<0时,return;因为此时所有m盆花已经被摆出去了,没有继续搜索的必要,直接return;

核心语句:

其实就一句话:

for(int j=0;j<=a[i];j++)
      dfs(rest-j,i+1);

直接暴力dfs因为实际并没有改变rest和i的值,所以我们连回溯都不需回溯;

以下是完整CODE:

CODE1:

#include<bits/stdc++.h>

using namespace std;

int n,m,ans,cnt;
int a[110],b[110];

void dfs(int rest/*还需要摆多少盆花*/,int i/*n种花*/){
    if(rest==0){
        ans++;ans%=1000007;
        return; 
    }
    if(i==n+1) return;
    if(rest<0) return;
        for(int j=0;j<=a[i];j++)
          dfs(rest-j,i+1);
    return;
}

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
      scanf("%d",&a[i]);
    dfs(m,1);
    cout<<ans<<endl;
}

但是非常遗憾的是暴搜显然不是正解,成功的TLE了7个点(功成身退

所以我们想到了记忆化搜索:

SOLUTION2:(记忆化搜索)

写暴搜的原因是因为并不会正解DP,因此只好暴搜了,然后又知道暴搜妥妥的TLE,所以在暴搜代码完成之后,改成了记忆化:

大致思路还是没有变,但dfs从没有返回值变成了带有返回值的dfs,当rest==0时(形成一种摆花条件时)return 1;否则return 0(对于最小子问题);对于每一次定义一个ans(当然也可以开全局然后每次重新赋值),枚举所有的摆花盆数,ans+=dfs(rest-j,i+1)[意思是对于某种花枚举所有可能的摆放盆数,统计剩余的空位置的总方案数,表示某种花摆j盆的总方案数(可能有点不对是非常绕)],记忆化数组b[i][rest]表示第i种花摆放前剩余rest个空位置(没有摆花)剩余rest个位置摆花的方案数,当我们已经计算过这个方案数以后,就可以直接拿来用而不是总是递归炸掉了。所以加一句

if(b[i][rest]) return b[i][rest];

如何计算b[i][rest]以及每一步的ans:

其实楼上两个东西是一回事,b[i][rest]=ans;

下面来看如何计算ans:首先枚举第i种花所有摆放盆数(0~a[i]),然后dfs(rest-j,i+1),不断将求得的值加到ans里,(ans记录的是当前要摆第i种花,此时还剩rest(第i种花还没摆)个位置时所有摆花的方案数)不要忘记取mod;

最后不要忘记return ans;

CODE2:

#include<bits/stdc++.h>

using namespace std;

long long n,m,cnt;
long long a[1100],b[1100][1100];
const int mod=1000007;

long long dfs(long long rest/*还需要摆多少盆花*/,long long i/*摆放n种花*/){
    if(rest==0)
        return 1; 
    if(i==n+1) return 0;
    if(rest<0) return 0;
    if(b[i][rest]) return b[i][rest];
    int ans=0;
        for(int j=0;j<=a[i];j++){
            if(rest-j<0) break;
            ans=(ans+dfs(rest-j,i+1))%mod;
        }
        b[i][rest]=ans;
    return ans;
}

int main(){
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++)
      scanf("%lld",&a[i]);
    cout<<dfs(m,1)%mod<<endl;
}

悄咪咪的粘上记忆化搜索的模板:(来自oi-wiki)

int g[MAXN];
int f(传入数值) {
  if (g[规模] != 无效数值) return g[规模];
  if (终止条件) return 最小子问题解;
  g[规模] = f(缩小规模);
  return g[规模];
}
int main() {
  ... memset(g, 无效数值, sizeof(g));
  ...
}

SOLUTION3:(正解-动态规划)

q姓神仙的题解【题解】

定义二维数组f[i][j]表示摆放i种花共j盆的最大方案数,初始状态是:f[i][0]=1;(因为不管是哪种花,只要不摆放,方案数均为1)这里虽然是二位数组,但DP时需要三重循环第一重循环i表示摆放i种花,第二重循环j表示这i种花共摆放多少盆(显然是1~m),第三重循环k,表示的是这种花摆放的盆数,(从0盆到a[i]盆),这里的第三层循环要从j到j-a[i]进行枚举转移方程就是f[i][j]+=f[i-1][k]%mod:

啥意思:

因为前i种花一共摆放j盆,那么假设第i种花摆放k1(k1∈(0,a[k1])盆,那么前面的i-1种花就摆放j-k1盆(所以第三层循环枚举的是前i-1种花摆放的方案数),那么所有的方案数相加就是摆放i种花j盆的最大方案数;

最后的答案就是f[n][m]%mod;(最后要%mod防止超出答案)

CODE3:

#include<bits/stdc++.h>

using namespace std;

int n,m;
int a[110],f[110][110];
const int mod=1000007;

int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++)
      scanf("%d",&a[i]);
    for(int i=0;i<=n;i++) f[i][0]=1;
    for(int i=1;i<=n;i++)//have n flower
      for(int j=1;j<=m;j++)//set out how many flower in total
        for(int k=j;k>=j-a[i];k--) {//each flower set out how many 
          if(k<0)break;
          f[i][j]+=f[i-1][k]%mod;
        }
    cout<<f[n][m]%mod;
}

真.end-

posted @ 2019-06-17 16:30  Sweetness  阅读(336)  评论(0编辑  收藏  举报