2024杭电多校(4) 1006. 延时操控【计数DP】

题意

给定一张 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 回合内将敌方生命值归零

1

思路

首先我们可以将问题转化为 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 hp5,这意味着敌方最多只会和己方不同步 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 dxi05dxidx0+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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值