2025迎春杯T3题解

· · 题解

作为 T3 的出题人,很荣幸此题作为此届迎春杯提交次数最多的一题。 由于此题在使用前经过了多次大改,导致题目描述混乱,在此致歉。

读完题目后,容易发现这是一个最短路问题,于是我们可以使用传统的 \text{bfs}\text{dfs} 来解决这个问题。当然,\text{dp} 也可解决此问题。

由于 \text{bfs} 较易实现,于是使用 \text{bfs} 来解决此问题。每次分别向上、向下、向右拓展(这里我们假设目标点在右侧),并加入队列。时间复杂度 \mathcal{O}(mpq^2)。此种方式运行速度较慢,理论上不可通过此题。但由于时间放的比较宽,部分代码可以通过

考虑优化。发现瓶颈在于我们每获得一个新点,都会枚举整个模拟战区中的所有位置,这会使许多点被重复访问,大大增加了复杂度。于是我们可以想到:当可以前往下一个战区时便前往,此时无需枚举。但此种优化效果不明显。

解法一:统一枚举。时间复杂度 \mathcal{O}(mpq)。lst 同学使用的便是此种解法,拿到了本题的最优解。即我们一行一行的处理,细节见代码:

int cnt = 0;
for ( int i = 1; i <= q; i ++ )
    if(use[x][i] == 1 && !p[x+1][i]) cnt++, use[x+1][i] = 1;
if ( cnt == 0 ) {
    ans ++;
    for ( int i = 1; i <= q; i ++ )
        if(!p[x][i] && !p[x+1][i]) use[x+1][i] = 1;
}

解法二:\text{dp}。时间复杂度 \mathcal{O}(mpq)。与上面没有本质区别,还不如上面的好写,不再赘述。

解法三:0/1-\text{bfs}。时间复杂度 \mathcal{O}(mpq)。我们把原问题抽象成为一个图,对于每个点,向左右两个点连边,并向所在行的代表点连边。规定进代表点与出代表点边权为 01。这样我们就实现了同行内两个位置的移动。同时,由于边权只有 01,我们可以使用 0/1-\text{bfs} 来解决这个问题。具体代码见下:

# include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7;
const int M = 1e6 + 7;
int n, m, p, q; int a[N], b[N], x[N], y[N];
struct edge {
    int nxt, v, w;
}e[M << 1];
int h[N << 1], cnt; 
inline void add_edge ( int u, int v, int w ) {
    e[++cnt].nxt = h[u], e[cnt].v = v, e[cnt].w = w;
    h[u] = cnt;
}
map <long long, bool> mp; int dis[M], t[M]; long long ans;
int main () {
    ios :: sync_with_stdio(false); cin.tie(0), cout.tie(0);
    cin >> p >> q >> n >> m;
    for ( int i = 1; i <= n; i ++ ) {
        cin >> x[i] >> y[i];
        mp[x[i] * q + y[i] - q] = 1;
    }
    for ( int j = 1; j <= m; j ++ ) cin >> a[j] >> b[j];
    for ( int i = 1; i <= p; i ++ ) {
        for ( int j = 1; j <= q; j ++ ) {
            if ( mp[i * q + j - q] ) continue;
            if ( i + 1 <= p && !mp[i * q + j] ) add_edge(i * q + j - q, i * q + j, 1);
            if ( i - 1 >= 1 && !mp[i * q + j - 2 * q] ) add_edge(i * q + j - q, i * q - 2 * q + j, 1); 
            add_edge(i * q + j - q, p * q + i, 0);
            add_edge(p * q + i, i * q + j - q, 1);
        }
    }
    for ( int j = 1; j <= p * q + p; j ++ ) t[j] = -1, dis[j] = 1e9;
    for ( int j = 0; j < m; j ++ ) {
        int k = a[j] * q + b[j] - q;
        if ( j == 0 ) k = 1;
        deque <int> dq; dq.push_front(k); dis[k] = 0, t[k] = j;
        while ( !dq.empty() ) {
            int u = dq.front(); dq.pop_front();
            for ( int i = h[u]; i; i = e[i].nxt ) {
                int v = e[i].v, w = e[i].w;
                if( t[v] != j )  t[v] = j, dis[v] = 1e9;
                if (dis[v] > dis[u] + w) {
                    dis[v] = dis[u] + w;
                    if ( w == 0 ) dq.push_front(v);
                    else dq.push_back(v);
                }
            }
        }
        ans += dis[a[j + 1] * q + b[j + 1] - q]; 
    }
    cout << ans << "\n";
    return 0;
}

附加问题一:若所有数据范围上限改为 10^6,此题如何实现?第一个在 2.10 前写出代码的人可以获得 50\text{rmb} 的奖励。不写代码口胡做法也可获得 10\text{rmb} 的奖励。

附加问题二:若所有数据范围上限改为 10^6,且去掉“依次攻击”的限制,此题如何实现?第一个在 2.10 前写出代码的人可以获得 50\text{rmb} 的奖励。不写代码口胡做法也可获得 10\text{rmb} 的奖励。