题意
你正在一栋有
n
(
n
≤
5000
)
n(n\le 5000)
n(n≤5000)层楼的大厦的第
a
a
a层,这栋楼里有一个秘密实验室在第
b
b
b层,你不能到达那一层楼。由于你闲得慌,所以决定做
k
k
k次电梯,每次到达一个楼层就在笔记本上的数列最后(初始的时候笔记本上没有数)写下当前的层数,请问
k
(
k
≤
5000
)
k(k\le 5000)
k(k≤5000)次之后笔记本上写的数列有几种可能性?
每次坐电梯的时候为了避免到达第
b
b
b层,需满足:假设目前在第
k
k
k层,要前往第
j
j
j层,则
∣
k
−
j
∣
<
∣
k
−
b
∣
|k-j|<|k-b|
∣k−j∣<∣k−b∣。
思路
朴素的DP
阶段性很明显,做一次电梯就是一个阶段,最容易想到的是 d p [ i ] [ j ] dp[i][j] dp[i][j]表示乘坐了 i i i次电梯,到了第 j j j层,有几种。使用push的更新方法比较符合正常逻辑,即用 d p [ i ] [ j ] dp[i][j] dp[i][j]去更新 d p [ i ] [ k ] ( 1 ≤ k ≤ n , ∣ j − k ∣ < ∣ j − b ∣ ) dp[i][k](1\le k \le n,|j-k|<|j-b|) dp[i][k](1≤k≤n,∣j−k∣<∣j−b∣)。这样空间上只要滚动数组即可,但时间上是 O ( n 2 k ) O(n^2k) O(n2k)。
优化转移
由于本蒟蒻差分学得不好,只能前缀和,这个时候就要用当前位置
j
j
j推出是从第几层
(
k
)
(k)
(k)过来的。用题目中的式子经过一番 艰难的 推导,得到:
所以只要用
s
u
m
sum
sum存储第
i
−
1
i-1
i−1次坐电梯后的状态的前缀和,就可以直接得到
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]。
注意:
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]不能由
d
p
[
i
−
1
]
[
j
]
dp[i-1][j]
dp[i−1][j]转移过来,所以在程序中要减去
d
p
[
j
]
dp[j]
dp[j]。
代码
如果不知道错哪里了的小伙伴可以看看彩蛋,说不定会有帮助。
#include<bits/stdc++.h>
using namespace std;
#define mod 1000000007
int n,a,b,k;
long long dp[5005],sum[5005],ans;
int main(){
cin>>n>>a>>b>>k;
dp[a]=1;
for(int i=a;i<=n;i++) sum[i]=1;
for(int i=1;i<=k;i++){
for(int j=1;j<b;j++) dp[j]=(sum[(b+j+1)/2-1]-dp[j]+mod)%mod;
for(int j=b+1;j<=n;j++) dp[j]=(sum[n]-sum[(b+j)/2]-dp[j]+2*mod)%mod;
for(int j=1;j<=n;j++) sum[j]=(sum[j-1]+dp[j])%mod;
}
for(int i=1;i<=n;i++) ans=(ans+dp[i])%mod;
cout<<ans<<endl;
return 0;
}
彩蛋
给大家讲个笑话,我第一遍写错是因为没有把式子倒过来,直接用满足
∣
k
−
j
∣
<
∣
k
−
b
∣
|k-j|<|k-b|
∣k−j∣<∣k−b∣的
j
j
j去转移
k
k
k,
j
j
j和
k
k
k完全反了。
第二遍是因为转移的时候做了减法,没有加上
m
o
d
mod
mod,变成负数了。
第三遍是因为我认为
⌊
b
+
j
2
⌋
\lfloor\frac{b+j}{2}\rfloor
⌊2b+j⌋在C++中应该写作
b
+
j
−
1
2
\frac{b+j-1}{2}
2b+j−1,因为上取整是
b
+
j
+
1
2
\frac{b+j+1}{2}
2b+j+1嘛,其实是
b
+
j
2
\frac{b+j}{2}
2b+j。
(自我诊断可能是因为刚退烧,脑子不太正常)