生命- DP
作者:互联网
Description
数轴上有一排N-1棵小树苗,位置分别在1.5,2.5,3.5……,i+0.5,……(N-1)+0.5。
有一天,由于间田同学施肥过多,小树苗们全部枯死了。但是不要紧,间田同学是魔法师,他掌握着生命的力量。对于一个闭区间内的小树苗,他可以使用魔法复活它们。但是会消耗一定的AP值。消耗的AP值定义为区间右端点减去左端点(右端点大于左端点,即消耗的AP值至少为1)。比如对于区间[3,5]使用魔法,消耗的AP值为5-3=2。
同时,由于一些无可奉告的原因,每一个端点只能作为一个区间的开头或结尾,且每次施法的左右端点只能是1到N之间的整数。
以上情况合法。
以上情况由于某一个点成为了多个区间的开头或结尾而不合法。
间田同学有K的AP值,消耗完后他就不能再使用魔法了,即随时保证AP值大于等于0。现在他想知道,他有多少种方案能够将所有小树苗全部复活。
对于两个方案,如果操作集合相同,则视为两个相同的方案。比如“先复活区间[1,3]的树苗再复活区间[2,4]的树苗”与“先复活区间[2,4]的树苗再复活区间[1,3]的树苗”视为同一种方案。空集不算一个合法方案。
小树苗可以多次被复活,效果与被复活一次相同,但是没有被魔法波及到的小树苗将一直保持枯死状态。
Input
第一行两个正整数N,K,代表小树苗的个数与间田同学初始AP值。
Output
输出一行一个整数,表示使得1到N所有小树苗都复活的方案数(对10^9+7取模)。
Sample Input
4 4
Sample Output
3
【样例解释】
可行不同的操作集合为{[1,4]},{[1,4],[2,3]},{[1,3],[2,4]}。
Hint
【数据规模及约定】
对于30%的数据,1≤N≤10,1≤K≤50;
对于40%的数据,1≤N≤14,1≤K≤50;
对于50%的数据,1≤N≤16,1≤K≤500;
对于80%的数据,1≤N≤50,1≤K≤500;
对于90%的数据,1≤N≤50,1≤K≤5000;
对于100%的数据,1≤N≤100,1≤K≤100000。
思路
- 注意到消耗的Ap值=sigma(右括号位置) – sigma(左括号位置)
- dp[i][j][k]表示当前在位置i,前面有j个开头,当前选择的括号的【sigma(右括号位置) -sigma(左括号位置)的值】为k。
- 枚举当前位置是左括号、右括号或者不放括号,分别从 dp[i - 1][j - 1][k - i]、dp[i - 1][j + 1][k + i]、dp[i - 1][j][k],放右括号可与前面任意一个左括号匹配,会产生j倍的答案贡献。
- 滚动数组优化时间
- K有效范围开5000~6000,多余的魔法值用不完
- ex为偏移量,防止数组下标k为负
代码
#include <iostream>
#include <cstdio>
#define ex 3000
using namespace std;
const int Mod=1e9+7;
int n,K;
long long ans,dp[2][105][6005];
int main()
{
scanf("%d%d",&n,&K);
dp[1][1][1+ex]=1;
for(int i=2;i<=n;++i)
for(int j=0;j<=i;++j)
for(int k=0;k<=ex*2;++k)
{
dp[i&1][j][k]=0;
if(j>=1&&k-i>=0) dp[i&1][j][k]=(dp[(i-1)&1][j][k]+dp[(i-1)&1][j-1][k-i])%Mod;//不放+放左
if((j>=1||i==n)&&k+i<=2*ex) dp[i&1][j][k]=(dp[i&1][j][k]+1LL*dp[(i-1)&1][j+1][k+i]*(j+1)%Mod)%Mod;//放右
}
for(int i=0;i>=max(-K,-ex);--i) ans=(ans+dp[n&1][0][i+ex])%Mod;
printf("%lld\n",ans);
return 0;
}
标签:小树苗,生命,AP,括号,DP,端点,复活,dp 来源: https://www.cnblogs.com/wuwendongxi/p/13766702.html