【杂题题解】2026年05月21日
P3164 [CQOI2014] 和谐矩阵
根据题目所述,我们可以得到类似 \(a_{x,y}\operatorname{xor}a_{x+1,y}\operatorname{xor}a_{x-1,y}\operatorname{xor}a_{x,y+1}\operatorname{xor}a_{x,y-1}=0\) 的方程。
一共有 \(n\times m\) 个点,对于每个点都可以列出类似上述的方程。
然后套高斯消元模板就行了,注意异或在更新每一行的时候要异或第一行,使得所有行的行首均为 \(0\)。
然后时间复杂度为 \(\Theta(\textup{未知数数量})^3\),即 \(\Theta(n^3m^3)\)。
然后 \(1\le n,m\le 40\),所以考虑 bitset 存储每一个方程,使得复杂度变为 \(\Theta(\frac{n^3m^3}{w})\)。
#include <bits/stdc++.h>
using namespace std;
const int N = 50;
const int dx[] = { 0, 0, 0, -1, 1 };
const int dy[] = { 0, -1, 1, 0, 0 };
int n, m;
int id[N][N];
int ans[N * N];
// 每个元素的缩写,类似状压
bitset <N * N> a[N * N];
void gauss() {
int tot = n * m;
for (int i = 1; i <= tot; i++) {
for (int j = i; j <= tot; j++) {
if (a[j][i]) {
swap(a[i], a[j]);
break;
}
}
if (!a[i][i]) {
ans[i] = 1;
continue;
}
for (int j = i + 1; j <= tot; j++)
if (a[j][i]) a[j] ^= a[i];
}
for (int i = tot; i >= 1; i--) {
for (int j = i + 1; j <= tot; j++) {
ans[i] ^= ans[j] * a[i][j];
}
}
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
id[i][j] = (i - 1) * m + j;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
int u = id[i][j];
for (int k = 0; k <= 4; k++) {
int x = i + dx[k];
int y = j + dy[k];
if (x < 1 || y < 1 || x > n || y > m) continue;
a[u][id[x][y]] = 1;
}
}
}
gauss();
for (int i = 1; i <= n * m; i++) {
cout << ans[i] << " ";
if (i % m == 0) puts("");
}
return 0;
}
P3851 [TJOI2007] 脱险
题目大意:给出一个矩阵图,给出一些起点与终点,起点上有人,每一个终点在一单位时间内只能被一个人通过,给出时限 \(T\),求能在时限内通过终点的人的最大数量。
题目指出空地可以容纳任意多的探险队员,这意味着队员在移动过程中不会互相发生阻挡。也就是说,只要一个队员能在规定的时间 \(T\) 内到达某个出口,他就可以在这个出口附近或空地上“等待”,直到轮到他出去。那么文章就要作在出口上了。
一个出口在 \(1\) 个单位时间内只能允许 \(1\) 名队员通过。因此,我们可以把“某个出口在第 \(i\) 秒”看作是一个独立的资源,容量为 \(1\)。
问题变成了将“探险队员”分配给“出口的各个时间点”:如果一个队员到达某个出口的最短时间为 \(d\),那么他可以选择该出口在第 \(d\) 秒、第 \(d+1\) 秒、……、直到第 \(T\) 秒的任意一个时间点出去。
然后有一些性质:
- 每个队员最多只能出去一次(需要 \(1\) 的流量)。
- 每个出口在每个时间点最多只能出去一个人(提供 \(1\) 的流量)。
- 这就构成了一个经典的二分图最大匹配模型,可以使用 Dinic 最大流算法来求解。
因此就可以建图了。
将地图上的每一个格子看作一个节点,对于相邻的非障碍物格子之间连边,边权为 \(1\)。之后使用 Floyd 求出任意两点间的距离 dis。
接着设定超级源点 \(S\) 和超级汇点 \(T\)。
从 \(S\) 向每个“初始有队员的格子”连一条容量为 \(1\) 的边,表示每个队员最多只能逃生一次。
对于每一个出口和每一个时间单位 \(i \in [1, T]\),在图中新建一个节点,代表“该出口在第 \(i\) 秒的状态”,并从该节点向 \(T\) 连一条容量为 \(1\) 的边。
如果某个队员到达某个出口的最短时间 \(dist \le i\),说明他赶得及在第 \(i\) 秒从该出口逃生(即使早到也可以等待),因此从该队员的节点向对应的“出口时间点”节点连一条容量为 \(1\) 的边。
之后跑一遍最大流就可以得到答案了。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 4e5 + 10;
const int M = 250;
const int inf = 0x3f3f3f3f3f3f3f3f;
// S: 超级源点, T: 超级汇点, tot: 网络流图中的总节点数
// dist: 任意两点间的最短路径距离
int n, m, t, S, T, dep[N], vis[N];
int ans, dist[M][M], tot;
char v[N]; // 存储地图字符,转化为一维下标
struct edge {
int x, v, id; // x: 终点, v: 残余容量, id: 反向边在目标节点邻接表中的索引
};
int top = 1;
int head[N], cur[N];
struct node {
int v, w;
int nxt;
} edge[N * 2];
void add(int u, int v, int w) {
edge[++top] = { v, w };
edge[top].nxt = head[u];
head[u] = top;
}
vector<int> cv, pp; // cv: 记录所有出口的位置, pp: 记录所有队员的初始位置
bool bfs() {
queue<int> q;
for (int i = 1; i <= tot; i++) {
cur[i] = head[i];
vis[i] = 0, dep[i] = inf;
}
vis[S] = 1, dep[S] = 1, q.push(S);
while (!q.empty()) {
int x = q.front();
q.pop();
vis[x] = 0;
// 分层
for (int i = head[x]; i; i = edge[i].nxt) {
int d = edge[i].v;
if (dep[d] > dep[x] + 1 && edge[i].w) {
dep[d] = dep[x] + 1;
if (!vis[d]) {
vis[d] = 1;
q.push(d);
}
}
}
}
return (dep[T] != inf);
}
int dfs(int x, int now) {
if (x == T) {
ans += now;
return now;
}
int used = 0;
// Dinic 快就快在一次可以找多条
for (int i = cur[x]; i; i = edge[i].nxt) {
cur[x] = i;
int v = edge[i].v, w = edge[i].w;
if (dep[v] == dep[x] + 1 && w) {
int res = dfs(v, min(w, now - used));
if (res) {
used += res;
edge[i].w -= res;
edge[i ^ 1].w += res;
if (used >= now) break;
}
}
}
return used;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m >> t;
// 初始化最短路距离矩阵
for (int i = 1; i <= n * m; i++)
for (int j = 1; j <= n * m; j++)
dist[i][j] = 1e18;
for (int i = 1; i <= n * m; i++)
dist[i][i] = 0;
// 读入地图,并将二维坐标转换为一维编号,建立网格图
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
int p = (i - 1) * m + j; // 一维编号
cin >> v[p];
if (v[p] == 'O') cv.push_back(p); // 收集出口
if (v[p] == 'P') pp.push_back(p); // 收集队员
// 左右相邻(非障碍物)连边
if (j > 1 && v[p] != '*' && v[p - 1] != '*')
dist[p][p - 1] = dist[p - 1][p] = 1;
// 上下相邻(非障碍物)连边
if (i > 1 && v[p] != '*' && v[p - m] != '*')
dist[p][p - m] = dist[p - m][p] = 1;
}
}
// Floyd算法计算网格图中任意两点之间的最短路距离
for (int k = 1; k <= n * m; k++) {
for (int i = 1; i <= n * m; i++) {
if (dist[k][i] == 1e18) continue;
for (int j = 1; j <= n * m; j++) {
if (dist[k][j] == 1e18) continue;
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
}
}
}
// 分配网络流图节点编号
S = n * m + 1; // 超级源点
T = n * m + 2; // 超级汇点
tot = n * m + 2; // 初始总节点数
// 核心建图步骤:拆解每一个出口的每一个时间状态
for (int i = 1; i <= t; i++) { // 枚举时间 1~T
for (int j = 0; j < cv.size(); j++) { // 枚举出口
int x = cv[j];
tot++; // 新建节点代表 "出口 x 在第 i 秒" 的状态
for (int k = 0; k < pp.size(); k++) { // 枚举所有的队员
int y = pp[k];
// 如果队员 y 到出口 x 的最短时间 <= i
// 说明队员赶得及在这个时间点从这个出口出去,连一条容量为 1 的边
if (dist[x][y] <= i) {
add(y, tot, 1);
add(tot, y, 0);
}
}
// "出口 x 在第 i 秒" 这个状态向汇点 T 连一条容量为 1 的边
// 限制了该出口在每一秒钟最多只能通过 1 人
add(tot, T, 1);
add(T, tot, 0);
}
}
// 从源点 S 向每个队员连一条容量为 1 的边,限制每人只能出去一次
for (int i = 0; i < pp.size(); i++) {
int x = pp[i];
add(S, x, 1);
add(x, S, 0);
}
// 执行 Dinic 算法求最大流
while (bfs()) dfs(S, 1e18);
// 输出最大逃跑人数
cout << ans << endl;
return 0;
}

浙公网安备 33010602011771号