http://codeforces.com/contest/479/problem/E
dp,我这dp弱狗都看出来了,这题dp不是很难想到,比较不容易的是如何优化,使得不会T。
题意:
给定一个启示的楼层a,有一个不能去的楼层b,对于你可以去的下一个楼层必须满足你当前楼层x与下一个要去的楼层y的距离小于x到b的距离。求出走k趟的方案数。
思路:
可以定义dp[i][j]表示第i趟的时候到第 j 楼层的方案数。
转移方程dp[i][j] += dp[i-1][k] (k = 1,….,n)
很容易写出一个O(
n3
)的算法,对于每一趟枚举每一个楼层,再对于每个楼层枚举前一个楼层,判断是否可以转移。
但是发现
105
并不可搞。
优化方案:
会发现查找前一个楼层的时候需要O(
n
),复杂度太高。但是仔细观察会发现对于当前的楼层,上一个楼层能转移到当前楼层的地方都很有规律,都是成段的。这样就可以运用一个比较常见的优化方法了,就是运用前缀数组来记录在前一趟中到达这个点以前的方案总数有几种。这样对于任意一个成段的满足的线段都可以求出在这个区间内的方案总数了,将转移状态的复杂度降至O(
在转移完之后要记得扣掉前一趟也在这个楼层之中的情况。
模运算注意事项:
这题还涉及到取模运算,在取模运算的时候要小心,如果是两个取模后的值相减再取模,很可能会出现负数,可以加上一个mod,以消除影响。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define M 5009
#define mod 1000000007
typedef long long ll;
int dp[M][M];
int pre[M];
int ans;
int n,a,b,kk;
int main()
{
while(scanf("%d %d %d %d",&n,&a,&b,&kk)==4)
{
ans = 0;
memset(dp,0,sizeof(dp));
dp[0][a] = 1;
for(int i = 1;i <= kk;i++)
{
pre[0] = 0;
for(int j = 1;j <= n;j++)
pre[j] = (pre[j-1]+dp[i-1][j])%mod; //记录前一趟中j以前的点(包括j)的方案总数
for(int j = 1;j <= n;j++)
{
if(j == b) continue;
if(j < b)
{
dp[i][j] = (pre[(abs(j-b)-1)/2+j]+mod)%mod; //小心小于而不能等于的条件
}
else
{
dp[i][j] = (pre[n] - pre[j-(abs(j-b)+1)/2]+mod)%mod;
}
dp[i][j] = (dp[i][j] - dp[i-1][j]+mod)%mod; //取模相减的时候记得要+mod以消除影响
}
}
int b;
for(int i = 1;i <= n;i++)
ans = (ans + dp[kk][i])%mod;
printf("%d\n",ans);
}
return 0;
}