矩阵游戏 题解
2026-08-15 10:09:44
发布于:湖北
5阅读
0回复
0点赞
题目链接:「联合省选 2021 A」矩阵游戏
「联合省选 2021 A」矩阵游戏 题解
题目描述
给定一个 的矩阵 ,其中
要求还原一个 的非负整数矩阵 ,且每个元素不超过 。若不存在,输出 NO,否则输出 YES 并给出任意一个合法矩阵。
算法分析
1. 构造初始解
先忽略大小限制,任意求出一组满足方程的解。令 ,并设第一行和第一列均为 ,然后利用递推式:
可以依次求出所有 。记该初始解为 (可能含有负数或超过 )。
2. 调整变量保持 不变
对于任意一组 ()和 (),定义
则新的 仍然满足所有 方程,因为 在 块中交替正负,求和后抵消。
因此问题转化为:寻找整数(或实数),使得对所有格子都有
3. 转化为差分约束
令 ,则约束等价于:
- 若 :
- 若 :
设变量 (),()。则 。
于是每个约束形如
即
这是典型的差分约束系统:对于不等式 ,连边 ,权值为 。若图中存在负环,则无解。
4. 求解与构造
- 添加超级源点 ,向所有变量连权值为 的边,以消除孤立点。
- 使用 SPFA 求单源最短路,同时检测负环。
- 若无负环,则得到一组可行解 ,。
- 最终矩阵为:
输出即可。
5. 复杂度
每个格子产生两条约束边,总边数 ,节点数 ,SPFA 在随机数据下表现良好,可通过本题。
Code:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 305;
const int MAXV = 1e6;
const ll INF = 4e18;
int n, m;
ll b[MAXN][MAXN]; // b 矩阵 (n-1)*(m-1)
ll f[MAXN][MAXN]; // 初始解
struct Edge {
int to;
ll w;
};
vector<Edge> G[MAXN * 2]; // 节点数: n + m + 1 (0为超级源点)
void addEdge(int u, int v, ll w) {
G[u].push_back({v, w});
}
bool spfa(int tot) {
vector<ll> dist(tot, INF);
vector<int> cnt(tot, 0);
vector<bool> inq(tot, false);
queue<int> q;
dist[0] = 0;
q.push(0);
inq[0] = true;
while (!q.empty()) {
int u = q.front(); q.pop();
inq[u] = false;
for (auto &e : G[u]) {
int v = e.to;
ll w = e.w;
if (dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
if (!inq[v]) {
q.push(v);
inq[v] = true;
if (++cnt[v] > tot) {
return false; // 存在负环
}
}
}
}
}
return true;
}
void solve() {
cin >> n >> m;
for (int i = 1; i < n; ++i)
for (int j = 1; j < m; ++j)
cin >> b[i][j];
// 构造初始解 f (第一行第一列为0)
memset(f, 0, sizeof(f));
for (int i = 1; i <= n; ++i) f[i][1] = 0;
for (int j = 1; j <= m; ++j) f[1][j] = 0;
for (int i = 1; i < n; ++i) {
for (int j = 1; j < m; ++j) {
f[i+1][j+1] = b[i][j] - f[i][j] - f[i][j+1] - f[i+1][j];
}
}
// 清空图
int tot = n + m + 1;
for (int i = 0; i < tot; ++i) G[i].clear();
// 超级源点连边
for (int i = 1; i <= n + m; ++i) addEdge(0, i, 0);
// 建差分约束边
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
int sign = ((i + j) & 1) ? -1 : 1; // (-1)^{i+j}
int u = i; // x_i
int v = n + j; // -y_j
ll val = f[i][j];
if (sign == 1) {
// 约束: -val <= x_i + y_j <= 1e6 - val
// 即 x_i - (-y_j) >= -val, <= 1e6 - val
// 转化为边: v -> u 权 val, u -> v 权 1e6 - val
ll L = -val;
ll U = MAXV - val;
addEdge(v, u, -L);
addEdge(u, v, U);
} else {
// 约束: val - 1e6 <= x_i + y_j <= val
ll L = val - MAXV;
ll U = val;
addEdge(v, u, -L);
addEdge(u, v, U);
}
}
}
if (!spfa(tot)) {
cout << "NO\n";
return;
}
// 获得解
vector<ll> dist(tot);
for (int i = 0; i < tot; ++i) dist[i] = (i == 0 ? 0 : INF);
// 实际上 spfa 已经计算出 dist,但这里重新获取
// 重新运行一次无负环的 spfa 求 dist
auto get_dist = [&]() -> vector<ll> {
vector<ll> dist(tot, INF);
vector<int> cnt(tot, 0);
vector<bool> inq(tot, false);
queue<int> q;
dist[0] = 0;
q.push(0);
inq[0] = true;
while (!q.empty()) {
int u = q.front(); q.pop();
inq[u] = false;
for (auto &e : G[u]) {
int v = e.to;
ll w = e.w;
if (dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
if (!inq[v]) {
q.push(v);
inq[v] = true;
}
}
}
}
return dist;
};
vector<ll> dist2 = get_dist();
// 计算答案矩阵
vector<vector<ll>> ans(n+1, vector<ll>(m+1));
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
int sign = ((i + j) & 1) ? -1 : 1;
ll x = dist2[i];
ll y = -dist2[n + j];
ll val = f[i][j] + sign * (x + y);
ans[i][j] = val;
}
}
cout << "YES\n";
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
cout << ans[i][j] << (j == m ? '\n' : ' ');
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
注意事项:
- 所有变量使用
long long防止中间计算溢出。 - 差分约束中的边权可能为负, 需正确检测负环。
- 初始解 f 的构造无特殊限制,但需保证所有格子被计算。
- 最终输出的每个元素应在 内,若因浮点误差导致微小偏差,可四舍五入,但本题均为整数,不会有误差。
该算法时间复杂度为 ,其中 ,在 时完全可行。
这道题样例错误,应输出以下:
YES
4 0 0
0 24 1
0 0 0
YES
1 0 2
2 12 0
0 0 0
NO
两种答案逻辑上两者都正确。
这里空空如也







有帮助,赞一个