问题描述(1S, 128M)
无所事事的Cinzo决定用坐电梯的方式来打发时间。他住在一个N层的房子中,最底下为1层,最高处为N层。他从他家所在的第A层出发,并决定连续坐K次电梯。
但由于迷信的缘故,B在中国被视为是不幸运的,所以整座楼并没有第B层。也是因为这个原因,如果Cinzo想从第X层出发到达第Y层,他希望Y能满足|X - Y| < |X - B|。
每次电梯到达后,Cinzo都会将电梯所到的层数记录在小本子上;K次电梯都坐完后,他将得到一个长度为K的数列。现在,Cinzo想知道,他可能写出多少个不同的数列?
输入格式(lift.in)
一行四个整数,N,A,B,K,分别代表电梯的层数,Cinzo最初的位置,不幸运的层数,以及乘坐电梯的次数。
输出格式(lift.out)
一个整数,代表不同的数列数。(结果对1000,000,007取模)
样例输入
5 2 4 2
样例输出
2
数据范围与约束
对于20%的数据,N<=10, K<=5;
对于60%的数据,N,K<=100;
对于100%的数据,N,K<=5000。
························································································································································································································································
这是一道(我觉得难的)dp题;
用f[i][j]表示做了 i 次,到第 j 层的方案数。
显然,f[i][j]可以由一步可以跳到的楼层转移过来。
那么就可以得到转移方程:
f[i][j]=Σf[i-1][j-dis~j+dis] ,(dis=abs(j-b)-1)
直接一个一个地加,时间复杂度是O(n^2*k) , 这样可以过60%。
我们可以发现,每一次需要加的这一段是连续的,(考虑出一个问题,在数组A[l,r]上都加上x,那么我们可以用差分,最后对记差分的数组求一遍前缀和就是A数组中真正的值。),那么我们就可以用 差分,以为每次f[i][],一开始都是0,所以直接对f[i][1~n]数组记差就可以了。这样时间复杂度是O(n*k)。
我们可以发现,每次我们往下dp时,只用到前1层的数据,那么我们可以用滚动数组来记录,这样就可以降低空间复杂度了。
需要注意一个问题,因为用的是滚动数组,所以要特别注意最后答案存在哪里。这可能是0分与100分的差别!
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<cstdio>
#include<queue>
#include<cmath>
#define MOD 1000000007
#define LL long long
using namespace std;
int f[2][5009];//f[5009][5009]-->f[i][j]是第i次,到第j层,方案数
int n,a,b,k,ans;
int l[5009],r[5009];//记录每个位置跳的边界
int main()
{
scanf("%d%d%d%d",&n,&a,&b,&k);
for(int i=1;i<=n;i++)
{
int dis=abs(i-b)-1;
l[i]=max(1,i-dis);r[i]=min(n,i+dis);
}
int e1=0,e2=1;
f[0][a]=1;
for(int i=1;i<=k;i++)
{
for(int j=1;j<=n;j++) f[e2][j]=0;
for(int j=1;j<=n;j++)
{
(f[e2][l[j]]+=f[e1][j])%=MOD;
(f[e2][r[j]+1]-=f[e1][j])%=MOD;
}
for(int j=1;j<=n;j++) (f[e2][j]+=f[e2][j-1])%=MOD;
for(int j=1;j<=n;j++) (f[e2][j]-=f[e1][j])%=MOD;//减去不跳的情况
swap(e1,e2);
}
for(int i=1;i<=n;i++) if(i!=b)
(ans+=f[e1][i])%=MOD;
ans=(ans+MOD)%MOD;//因为中间有减法, 以后也是加上好
printf("%d",ans);
return 0;
}