AtCoder Beginner Contest 453
D - Go Straight
题意:
这是一个在 \(H \times W\) 网格地图上的路径寻找问题,但加入了特殊的移动规则:
- 单元格类型:
#:障碍物,不可进入。.:普通路,可以自由进出。o:强制直行。进入该格后,下一步必须保持和进入时相同的方向。x:强制转弯。进入该格后,下一步不能走和进入时相同的方向(必须左转、右转或掉头)。S和G:起点和终点。
- 目标:判断能否从起点到达终点,如果可以,输出一条步数在 \(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\) 两个不同的队伍中。
必须满足的条件:
- 非空:每个队伍至少要有一名球员。
- 唯一性:每个球员必须且只能属于一个队伍(要么在 \(A\),要么在 \(B\))。
- 人数约束:对于每一位球员 \(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. 总结执行步骤
- 区间修改:遍历每个球员,根据其 \([L_j, R_j]\),利用差分数组在 \(cnt_A, cnt_B, cnt_C\) 的对应区间打上标记。
- 前缀和还原:通过一次遍历,将差分数组还原成普通数组,此时我们就得到了每一个 \(i\) 对应的 \(cnt_A[i], cnt_B[i], cnt_C[i]\)。
- 累加答案:遍历所有的 \(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;
}

浙公网安备 33010602011771号