题意
给定一张
n
×
n
n\times n
n×n 的网格图,有些方格是障碍(#),有些是空地(.),恰有一名己方角色
P
P
P 和敌方角色
E
E
E,在每回合中己方角色可以 上下左右 移动,但是己方角色不能移动到障碍内或者地图外
敌方角色会重复己方角色
k
k
k 回合前的移动,敌方初始有
h
p
hp
hp 点生命值,每次移动失败都会扣除
1
1
1 点生命值,并留在原地
统计有多少种不同的移动方案,使得在不超过 m m m 回合内将敌方生命值归零
思路
首先我们可以将问题转化为 k = 0 k = 0 k=0 即无延迟的情况,在敌人生命值减为 0 0 0 后记录一下己方角色对应的位置,最后将所有的情况在此基础上随机游走 k k k 轮(因为最后 k k k 条移动不会影响敌人),即为最终答案。
最朴素的想法就是:定义 d p [ i ] [ p x ] [ p y ] [ h ] [ e x ] [ e y ] dp[i][px][py][h][ex][ey] dp[i][px][py][h][ex][ey] 表示当前进行了 i i i 个回合,己方角色位于 ( p x , p y ) (px, py) (px,py),敌方位于 ( e x , e y ) (ex, ey) (ex,ey),且敌方扣除了 h h h 点生命值。这样子进行转移的话,复杂度是 O ( m n 4 h p ) O(mn^4hp) O(mn4hp),太高…
进一步观察发现:
h
p
≤
5
hp \leq 5
hp≤5,这意味着敌方最多只会和己方不同步
5
5
5 次,其他移动都是同步的,那么如果我们计算一下初始时刻己方和敌方的位置矢量差
(
d
x
0
,
d
y
0
)
(dx_0, dy_0)
(dx0,dy0),即
p
x
+
d
x
=
e
x
,
p
y
+
d
y
=
e
y
px + dx = ex, py + dy = ey
px+dx=ex,py+dy=ey,那么我们在移动过程中,维护
(
d
x
i
,
d
y
i
)
(dx_i, dy_i)
(dxi,dyi),就可以得出
(
e
x
,
e
y
)
(ex, ey)
(ex,ey)。
结合刚刚说的,敌方最多
5
5
5 次与己方不同步,那么
d
x
i
0
−
5
≤
d
x
i
≤
d
x
0
+
5
dx_{i0} - 5 \leq dx_i \leq dx_{0} + 5
dxi0−5≤dxi≤dx0+5,
d
y
i
dy_i
dyi 同理,因此我们将
d
p
dp
dp 数组的
(
e
x
,
e
y
)
(ex, ey)
(ex,ey) 改为这个
(
d
x
i
,
d
y
i
)
(dx_i, dy_i)
(dxi,dyi) 相对
(
d
x
0
,
d
y
0
)
(dx_0, dy_0)
(dx0,dy0) 的 范围在
[
−
5
,
5
]
[-5, 5]
[−5,5] 的增量
(
d
x
′
,
d
y
′
)
(dx^\prime, dy^\prime)
(dx′,dy′),就可以大规模压缩复杂度,即
e
x
=
p
x
+
d
x
0
+
d
x
′
ex = px + dx_0 + dx^\prime
ex=px+dx0+dx′,
e
y
ey
ey 同理
此时复杂度为: O ( m n 2 h p 3 ) O(mn^2hp^3) O(mn2hp3)
#include<bits/stdc++.h>
#define fore(i,l,r) for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define ull unsigned long long
#define ALL(v) v.begin(), v.end()
#define Debug(x, ed) std::cerr << #x << " = " << x << ed;
const int INF=0x3f3f3f3f;
const long long INFLL=1e18;
typedef long long ll;
template<class T>
constexpr T power(T a, ll b){
T res = 1;
while(b){
if(b&1) res = res * a;
a = a * a;
b >>= 1;
}
return res;
}
constexpr ll mul(ll a,ll b,ll mod){ //快速乘,避免两个long long相乘取模溢出
ll res = a * b - ll(1.L * a * b / mod) * mod;
res %= mod;
if(res < 0) res += mod; //误差
return res;
}
template<int P>
struct MLL{
int x;
constexpr MLL() = default;
constexpr MLL(int x) : x(norm(x % getMod())) {}
static int Mod;
constexpr static int getMod(){
if(P > 0) return P;
return Mod;
}
constexpr static void setMod(int _Mod){
Mod = _Mod;
}
constexpr int norm(int x) const{
if(x < 0){
x += getMod();
}
if(x >= getMod()){
x -= getMod();
}
return x;
}
constexpr int val() const{
return x;
}
explicit constexpr operator int() const{
return x; //将结构体显示转换为ll类型: ll res = static_cast<ll>(OBJ)
}
constexpr MLL operator -() const{ //负号,等价于加上Mod
MLL res;
res.x = norm(getMod() - x);
return res;
}
constexpr MLL inv() const{
assert(x != 0);
return power(*this, getMod() - 2); //用费马小定理求逆
}
constexpr MLL& operator *= (MLL rhs) & { //& 表示“this”指针不能指向一个临时对象或const对象
x = mul(x, rhs.x, getMod()); //该函数只能被一个左值调用
return *this;
}
constexpr MLL& operator += (MLL rhs) & {
x = norm(x + rhs.x);
return *this;
}
constexpr MLL& operator -= (MLL rhs) & {
x = norm(x - rhs.x);
return *this;
}
constexpr MLL& operator /= (MLL rhs) & {
return *this *= rhs.inv();
}
friend constexpr MLL operator * (MLL lhs, MLL rhs){
MLL res = lhs;
res *= rhs;
return res;
}
friend constexpr MLL operator + (MLL lhs, MLL rhs){
MLL res = lhs;
res += rhs;
return res;
}
friend constexpr MLL operator - (MLL lhs, MLL rhs){
MLL res = lhs;
res -= rhs;
return res;
}
friend constexpr MLL operator / (MLL lhs, MLL rhs){
MLL res = lhs;
res /= rhs;
return res;
}
friend constexpr std::istream& operator >> (std::istream& is, MLL& a){
ll v;
is >> v;
a = MLL(v);
return is;
}
friend constexpr std::ostream& operator << (std::ostream& os, MLL& a){
return os << a.val();
}
friend constexpr bool operator == (MLL lhs, MLL rhs){
return lhs.val() == rhs.val();
}
friend constexpr bool operator != (MLL lhs, MLL rhs){
return lhs.val() != rhs.val();
}
};
const int mod = 1e9 + 7;
using Z = MLL<mod>;
const int N = 55;
const int K = 5;
std::string mp[N];
int n, m, k, hp;
Z dp[2][N][N][K][1 + K << 1][1 + K << 1]; //滚动数组
Z rnd[2][N][N]; //怪物死亡后的随机游走,共 k 轮
std::vector<std::pair<int, int>> mv = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
inline bool check(int x, int y){ //检查位置合法性
return x > 0 && x <= n && y > 0 && y <= n && mp[x][y] != '#';
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int t;
std::cin >> t;
while(t--){
std::cin >> n >> m >> k >> hp;
int px, py, ex, ey;
fore(i, 1, n + 1){
std::cin >> mp[i];
mp[i] = '0' + mp[i];
fore(j, 1, n + 1){
if(mp[i][j] == 'P'){
px = i;
py = j;
}
else if(mp[i][j] == 'E'){
ex = i;
ey = j;
}
}
}
/* init */
auto init = [&](int now){
fore(i, 1, n + 1)
fore(j, 1, n + 1)
fore(h, 0, hp)
fore(dx, -h, h + 1){
int lim = h - (dx > 0 ? dx : -dx);
fore(dy, -lim, lim + 1)
dp[now][i][j][h][dx + K][dy + K] = 0;
}
};
init(0);
init(1);
fore(i, 1, n + 1)
fore(j, 1, n + 1)
rnd[0][i][j] = 0;
ex -= px;
ey -= py; //初始偏移量
int now = 0, old = 1; //滚动数组
dp[now][px][py][0][K][K] = 1; //初始状态
m -= k; //延迟为 0
while(m--){
std::swap(now, old);
init(now);
fore(i, 1, n + 1)
fore(j, 1, n + 1)
fore(h, 0, hp)
fore(dx, -h, h + 1){
int lim = h - (dx > 0 ? dx : -dx); //y的绝对值
fore(dy, -lim, lim + 1){
Z lst = dp[old][i][j][h][dx + K][dy + K]; //上一个状态
for(auto [d0, d1] : mv){ //枚举角色的移动
int px = i + d0;
int py = j + d1;
if(!check(px, py)) continue; //角色位置不合法
if(check(px + ex + dx, py + ey + dy)) //怪物位置合法
dp[now][px][py][h][dx + K][dy + K] += lst;
else if(h + 1 == hp) //怪物即将死亡
rnd[0][px][py] += lst;
else
dp[now][px][py][h + 1][dx - d0 + K][dy - d1 + K] += lst;
}
}
}
}
now = 0, old = 1;
while(k--){ //随机游走 k 轮
std::swap(now, old);
fore(i, 1, n + 1)
fore(j, 1, n + 1)
rnd[now][i][j] = 0;
fore(i, 1, n + 1)
fore(j, 1, n + 1)
for(auto [d0, d1] : mv){
if(!check(i + d0, j + d1)) continue;
rnd[now][i + d0][j + d1] += rnd[old][i][j];
}
}
Z ans = 0;
fore(i, 1, n + 1)
fore(j, 1, n + 1)
ans += rnd[now][i][j];
std::cout << ans << endl;
}
return 0;
}