Feed the monkey
Problem Description
Alice has a monkey, she must feed fruit to the monkey every day.She has three kinds of fruits, bananas,
peaches and apples. Every day, she chooses one in three, and pick one of this to feed the monkey.
But the monkey is picky, it doesn’t want bananas for more than D1 consecutive days, peaches for more than D2
consecutive days, or apples for more than D3 consecutive days. Now Alice has N1 bananas, N2 peaches and N3
apples, please help her calculate the number of schemes to feed the monkey.
Input
Multiple test cases. The first line contains an integer T (T<=20), indicating the number of test case.
Each test case is a line containing 6 integers N1, N2, N3, D1, D2, D3 (N1, N2, N3, D1, D2, D3<=50).
Output
One line per case. The number of schemes to feed the monkey during (N1+N2+N3) days.
The answer is too large so you should mod 1000000007.
Example Input
1 2 1 1 1 1 1
Example Output
6
Hint
Answers are BPBA, BPAB, BABP, BAPB, PBAB, and ABPB(B-banana P-peach A-apple)
Author
题意:给你三种水果的数量和每一个水果不能连续的数量,问有多少中组合方式。
思路:Dp[i][j][k][f]表示第一种水果剩余i个第二种水果剩余j个,第三种水果剩余k个,以f种水果结尾连续数量在规定范围内的组合种类,那么第一种水果为例,连续的为s个:Dp[i-s][j][k][0] += Dp[i][j][k][1]+Dp[i][j][2] ;
这是借鉴网友思路,不过好像是逆向思维了,不过之后自己再写一个第一种水果i个,第二种水果j个,第三种水果k个,以第f种水果为结尾的连续数量在规定范围内组合的种类,这样感觉好一些Dp[i+s][j][k][0] += Dp[i][j][k][1]+Dp[i][j][2] (s是当前连续添加的苹果的数量)
用连续的方式来想。。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL Mod = 1e9+7;
LL Dp[55][55][55][4];
int main()
{
int T;
int n1,n2,n3,d1,d2,d3;
scanf("%d",&T);
while(T--)
{
scanf("%d %d %d %d %d %d",&n1,&n2,&n3,&d1,&d2,&d3);
memset(Dp,0,sizeof(Dp));
for(int s = 1; s<=min(n1,d1); s++) ///对于开始情况初始化,即小于当前水果(Apple)且只有一种的初始化
Dp[n1-s][n2][n3][0] = 1;
for(int s = 1; s<=min(n2,d2); s++)
Dp[n1][n2-s][n3][1] = 1;
for(int s = 1; s<=min(n3,d3); s++)
Dp[n1][n2][n3-s][2] = 1;
for(int i = n1; i>=0; i--)
{
for(int j = n2; j>=0; j--)
{
for(int k = n3; k>=0; k--)
{
if(i == n1 && j == n2 && k == n3) continue ;
for(int s = 1; s<=min(i,d1); s++)
Dp[i-s][j][k][0] = ((Dp[i-s][j][k][0]+Dp[i][j][k][1])%Mod+Dp[i][j][k][2])%Mod;
for(int s = 1; s<=min(j,d2); s++)
Dp[i][j-s][k][1] = ((Dp[i][j-s][k][1]+Dp[i][j][k][0])%Mod+Dp[i][j][k][2])%Mod;
for(int s = 1; s<=min(k,d3); s++)
Dp[i][j][k-s][2] = ((Dp[i][j][k-s][2]+Dp[i][j][k][0])%Mod+Dp[i][j][k][1])%Mod;
}
}
}
LL ans = 0;
ans = ((Dp[0][0][0][0]+Dp[0][0][0][1])%Mod+Dp[0][0][0][2])%Mod;
printf("%lld\n",ans);
}
return 0;
}
学弟记忆化搜索的思路,我之前的最爱,总喜欢把DP想成记忆化搜索来做,原因很简单,记忆化搜索毕竟是搜索的范畴,好想一些(或者是搜索做多了,然后DP自己水平太渣!!)
#include <bits/stdc++.h>
#pragma warning(disable:4786)//使命名长度不受限制
//#pragma comment(linker, "/STACK:102400000,102400000")//手工开栈 ///很奇怪,不知道为什么手工开栈之后就超时
#define maxn 52
#define mod 1000000007
using namespace std;
int dp[maxn][maxn][maxn][3][maxn];
int A,B,C,a,b,c,sum;
inline int dfs(int x,int y,int z,int t,int n){
if(x>A||y>B||z>C||x+y+z>sum)return 0;
if(t==0&&n>a||t==1&&n>b||t==2&&n>c)return 0;
if(dp[x][y][z][t][n]!=-1)return dp[x][y][z][t][n];
if(x+y+z==sum)return 1;
int ans=0;
if(t==0){
ans=(ans+dfs(x+1,y,z,0,n+1))%mod;
ans=(ans+dfs(x,y+1,z,1,1))%mod;
ans=(ans+dfs(x,y,z+1,2,1))%mod;
return dp[x][y][z][t][n]=ans;
}
if(t==1){
ans=(ans+dfs(x+1,y,z,0,1))%mod;
ans=(ans+dfs(x,y+1,z,1,n+1))%mod;
ans=(ans+dfs(x,y,z+1,2,1))%mod;
return dp[x][y][z][t][n]=ans;
}
if(t==2){
ans=(ans+dfs(x+1,y,z,0,1))%mod;
ans=(ans+dfs(x,y+1,z,1,1))%mod;
ans=(ans+dfs(x,y,z+1,2,n+1))%mod;
return dp[x][y][z][t][n]=ans;
}
}
int main(){
int loop;
scanf("%d",&loop);
for(;loop--;){
scanf("%d%d%d%d%d%d",&A,&B,&C,&a,&b,&c);
sum=A+B+C;
memset(dp,-1,sizeof(dp));
int ans=0;
ans=(ans+dfs(1,0,0,0,1))%mod;
ans=(ans+dfs(0,1,0,1,1))%mod;
ans=(ans+dfs(0,0,1,2,1))%mod;
printf("%d\n",ans);
}
return 0;
}
总结:
可以明显的发现有以下几点不同:
1、DP是从下向上,而记忆化搜索是从上向下的
2、DP是从下向上,为了求到最终结果需要把过程中所有的值都保存下来,以便下一步可能会使用,而因为记忆化搜索是从上向下的,所以求解过程求的都是需要的;也就是说不需要的值并没有求
3、记忆化搜索使用递归实现的,从上面的代码可以看出
如果一个dp[i][j]的值已经求过,使用DP直接调用即可;而使用记忆化搜索则要进入递归
如果一个dp[i][j]的值还未求过,使用DP直接求得,而使用记忆化搜索则要进入递归中去求,而这个递归很有可能是多重的
这样一来DP在时间上几乎总是优于记忆化搜索的