【杂题题解】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;
}
posted @ 2026-05-26 20:36  chrispang  阅读(12)  评论(0)    收藏  举报