AtCoder Beginner Contest 453

D - Go Straight

题意:

这是一个在 \(H \times W\) 网格地图上的路径寻找问题,但加入了特殊的移动规则:

  • 单元格类型
    • #障碍物,不可进入。
    • .普通路,可以自由进出。
    • o强制直行。进入该格后,下一步必须保持和进入时相同的方向。
    • x强制转弯。进入该格后,下一步不能走和进入时相同的方向(必须左转、右转或掉头)。
    • SG:起点和终点。
  • 目标:判断能否从起点到达终点,如果可以,输出一条步数在 \(5 \times 10^6\) 以内的合法路径。

思路

带方向的BFS搜索题。

状态定义:不只是坐标

在普通网格搜索中,一个状态通常只是坐标 \((x, y)\)。但由于本题有“o(强制直行)”和“x(强制转弯)”的规则,下一步怎么走取决于你是从哪个方向进来的

  • 核心状态:用 (行坐标, 列坐标, 进入方向) 来表示。
  • 状态总数:最大为 \(1000 \times 1000 \times 4\)(四个方向),即 \(4 \times 10^6\) 种可能
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
char s[N][N];
int n, m;
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
int vis[N][N][4];
int pre[N][N][4];
char dir[4] = {'R', 'L', 'D', 'U'};
struct node {
    int x, y, d;
    // node(int x = 0, int y = 0, int d = 0) : x(x), y(y), d(d) {}
};
void Print_Path(int sx, int sy, int ex, int ey, int d) {
    vector<node> path;
    int x = ex, y = ey;
    int cnt = 0;
    while (x != sx || y != sy) {
        path.push_back({x, y, d});
        int pd = pre[x][y][d];
        x -= dx[d];
        y -= dy[d];
        d = pd;
        cnt++;
    }
  
    for (int i = path.size() - 1; i >= 0; i--) {
        cout << dir[path[i].d];
    }

    cout << endl;
}
void BFS(int sx, int sy, int ex, int ey) {
    queue<node> q;
    memset(pre, -1, sizeof(pre));
    q.push({sx, sy, 0});
    q.push({sx, sy, 1});
    q.push({sx, sy, 2});
    q.push({sx, sy, 3});
    vis[sx][sy][0] = vis[sx][sy][1] = vis[sx][sy][2] = vis[sx][sy][3] = 1;
    while (q.size()) {
        node t = q.front();
        q.pop();

        if (t.x == ex && t.y == ey) {
            cout << "Yes" << endl;
            Print_Path(sx, sy, ex, ey, t.d);
            return;
        }
        for (int i = 0; i < 4; i++) {
            int x = t.x + dx[i];
            int y = t.y + dy[i];
            if (x < 1 || x > n || y < 1 || y > m || s[x][y] == '#' ||
                vis[x][y][i])
                continue;
            if (s[t.x][t.y] == 'S' || s[t.x][t.y] == '.') {
                q.push({x, y, i});
                vis[x][y][i] = 1;
                pre[x][y][i] = t.d;
            } else if (s[t.x][t.y] == 'o') {
                if (t.d != i) continue;
                q.push({x, y, i});
                vis[x][y][i] = 1;
                pre[x][y][i] = t.d;
            } else if (s[t.x][t.y] == 'x') {
                if (t.d == i) continue;
                q.push({x, y, i});
                vis[x][y][i] = 1;
                pre[x][y][i] = t.d;
            }
        }
    }
    cout << "No" << endl;
}


int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> s[i][j];
        }
    }
    int sx, sy, ex, ey;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (s[i][j] == 'S') sx = i, sy = j;
            if (s[i][j] == 'G') ex = i, ey = j;
        }
    }
    BFS(sx, sy, ex, ey);
}

E - Team Division

题意:

\(N\) 名球员,需要将他们分配到 \(A\)\(B\) 两个不同的队伍中。

必须满足的条件:

  1. 非空:每个队伍至少要有一名球员。
  2. 唯一性:每个球员必须且只能属于一个队伍(要么在 \(A\),要么在 \(B\))。
  3. 人数约束:对于每一位球员 \(i\),他所在的那个队伍的总人数 \(k\) 必须满足:\(L_i \le k \le R_i\)
  • 目标:计算满足以上所有条件的分配方案总数,结果对 $99824

思路

1. 核心前提:枚举 A 队人数

首先,我们假设 \(N\) 个球员被分成 A、B 两队。如果我们固定 A 队的总人数为 \(i\) 人,那么 B 队的总人数必然是 \(N-i\) 人。

2. 对球员进行分类

对于每一个固定的 A 队人数 \(i\),我们可以把所有球员分成三类:

  • 只能去 A 队:该球员的要求满足 \(i \in [L_j, R_j]\),但不满足 \(N-i \in [L_j, R_j]\)
  • 只能去 B 队:该球员的要求满足 \(N-i \in [L_j, R_j]\),但不满足 \(i \in [L_j, R_j]\)
  • 两队都能去:该球员的要求既满足 \(i \in [L_j, R_j]\),又满足 \(N-i \in [L_j, R_j]\)

思路图中的定义稍微宽泛一些:

  • \(cnt_A\):能分配到 A 队的总人数(包括只能去 A 和两边都能去的)。
  • \(cnt_B\):能分配到 B 队的总人数(包括只能去 B 和两边都能去的)。
  • \(cnt_C\)自由人,即两边都能去的人数。

3. 计算特定人数下的方案数

当我们确定了 \(i\) 时,A 队必须包含那些“只能去 A 队”的人。

  • 只能去 A 队的人数 = \(cnt_A - cnt_C\)
  • A 队还需要的人数 = \(i - (cnt_A - cnt_C)\)
  • 这剩下的名额必须从那 \(cnt_C\) 个“自由人”中选出。

所以,当 A 队人数固定为 \(i\) 时,方案数为:

\[\binom{cnt_C}{i - (cnt_A - cnt_C)} \]

(注意:如果 \(cnt_A - cnt_C > i\) 或者剩下的自由人不够填满 A 队,则该 \(i\) 下方案数为 0)

4. 关键优化:从 \(O(N^2)\)\(O(N)\)

如果我们对每个 \(i\) 都重新遍历一遍所有球员来算 \(cnt_A, cnt_B, cnt_C\),时间复杂度是 \(O(N^2)\),会超时。

优化策略:利用差分数组

每个球员对 \(cnt_A, cnt_B, cnt_C\) 的贡献其实是连续的区间

  • \(cnt_A\) 的贡献:只要 A 队人数 \(i\)\([L_j, R_j]\) 内,该球员就能去 A。这相当于在 \(cnt_A\) 数组的 \([L_j, R_j]\) 区间全部 \(+1\)
  • \(cnt_B\) 的贡献:只要 B 队人数 \(N-i\)\([L_j, R_j]\) 内,即 \(i \in [N-R_j, N-L_j]\),该球员就能去 B。这相当于在 \(cnt_B\) 数组的 \([N-R_j, N-L_j]\) 区间全部 \(+1\)
  • \(cnt_C\) 的贡献:该球员两边都能去,要求 \(i\) 同时落在上述两个区间内。即 \(i \in [\max(L_j, N-R_j), \min(R_j, N-L_j)]\)。如果这个交集存在,则在该区间全部 \(+1\)

5. 总结执行步骤

  1. 区间修改:遍历每个球员,根据其 \([L_j, R_j]\),利用差分数组\(cnt_A, cnt_B, cnt_C\) 的对应区间打上标记。
  2. 前缀和还原:通过一次遍历,将差分数组还原成普通数组,此时我们就得到了每一个 \(i\) 对应的 \(cnt_A[i], cnt_B[i], cnt_C[i]\)
  3. 累加答案:遍历所有的 \(i \in [1, N-1]\),计算组合数 \(\binom{cnt_C}{i - (cnt_A - cnt_C)}\) 并累加到总和中。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
const int mod = 998244353;
#define LL long long
// #define int long long
int n, l[N], r[N];
int cnta[N], cntb[N], cntc[N];
int fact[N], invf[N];
int qmi(int a, int b) {
    int res = 1;
    while (b) {
        if (b & 1) res = 1LL * res * a % mod;
        a = 1LL * a * a % mod;
        b >>= 1;
    }
    return res;
}
void init() {
    fact[0] = 1;
    for (int i = 1; i < N; i++) {
        fact[i] = 1LL * fact[i - 1] * i % mod;
    }
    invf[N - 1] = qmi(fact[N - 1], mod - 2);
    for (int i = N - 2; i >= 0; i--) {
        invf[i] = 1LL * invf[i + 1] * (i + 1) % mod;
    }
}
int C(int a, int b) {
    if (a < b || b < 0) return 0;
    return 1LL * fact[a] * invf[b] % mod * invf[a - b] % mod;
}
signed main() {
    init();

    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> l[i] >> r[i];
        cnta[l[i]]++;
        cnta[r[i] + 1]--;

        int ll, rr;
        ll = n - r[i];
        rr = n - l[i];
        cntb[ll]++;
        cntb[rr + 1]--;

        int L, R;
        L = max(l[i], ll);
        R = min(r[i], rr);
        if (L <= R) {
            cntc[L]++;
            cntc[R + 1]--;
        }
    }
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        cnta[i] += cnta[i - 1];
        cntb[i] += cntb[i - 1];
        cntc[i] += cntc[i - 1];

        int left = cnta[i] - cntc[i];
        int right = cntb[i] - cntc[i];

        if (left > i || right > n - i) continue;
        if (i - left < 0) continue;
        if (cntc[i] < i - left) continue;
        if(left + right + cntc[i] != n) continue;
        ans = (1LL * ans + C(cntc[i], i - left)) % mod;
    }

    cout << ans << endl;
}
posted @ 2026-04-12 10:20  Wraith_G  阅读(16)  评论(0)    收藏  举报
// //