3. val
【题目描述】
这是一道传统题,源代码的文件名为 val.cpp/c/pas。
有一个值初始为 0,接下来 n 次你可以令其在之前基础上+2 或+1 或-1。你需要保证,这个值在整个过程中达到的最大值减去达到的最小值不大于 k,求方案数,模 1,000,000,007。
【输入格式】
从 val.in 中读入。
仅一行,两个空格隔开的正整数 n 和 k。
【输出格式】
输出到 val.out 中。
仅一行,一个非负整数,表示方案数对 1,000,000,007 取模后的结果。
【输入样例 A】
3 2
【输出样例 A】
11
【输入样例 B】
233 99
【输出样例 B】
316461264
【评分标准】
对于 10%的数据,n,k<=15;
对于 30%的数据,n,k<=75;
对于 50%的数据,n,k<=300;
对于另 10%的数据,k=1;
对于 100%的数据,n,k<=5,000。
时间限制 2s,空间限制 512MB。
题解:
这道题我一开始想的是直接O(3n)暴力搜索每一种情况,并记录最大最小值依次判断,但是这样做只能得20分。
考虑到本题中无后效性的特点,再想一想有最优子结构,于是想到此题正解是DP。
枚举最小值是 d,则只需要限制达到的值始终在 d 和 d+k 之间,且保证达到过 d 即可,于是每次枚举还需要一个 O(nk)的 dp。注意到这可以认为是从-d 出发,达到的值始终在 0 到k 之间,且保证过达到 0。这样子就不需要枚举 d,直接做一次 dp 就可以了。
代码(std):
1 #include <bits/stdc++.h> 2 #define rep(i,l,r) for(int i=l;i<=r;i++) 3 #define per(i,r,l) for(int i=r;i>=l;i--) 4 #define mo 1000000007 5 #define N 5005 6 int n,k,dp[N][N][2]; 7 int main() 8 { 9 char fni[]="val.in",fno[]="val.out"; 10 freopen(fni,"r",stdin); 11 freopen(fno,"w",stdout); 12 scanf("%d%d",&n,&k); 13 memset(dp,0,sizeof(dp)); 14 rep(i,0,k) dp[0][i][!i]=1; 15 rep(i,1,n) 16 { 17 rep(j,0,k) rep(t,0,1) 18 { 19 if(j>=2) (dp[i][j][t]+=dp[i-1][j-2][t])%=mo; 20 if(j>=1) (dp[i][j][t]+=dp[i-1][j-1][t])%=mo; 21 if(j<k) (dp[i][j][t]+=dp[i-1][j+1][t])%=mo; 22 } 23 (dp[i][0][1]+=dp[i][0][0])%=mo; 24 dp[i][0][0]=0; 25 } 26 int ans=0; 27 rep(i,0,k) (ans+=dp[n][i][1])%=mo; 28 printf("%d ",ans); 29 fclose(stdin); 30 fclose(stdout); 31 }