Matrix Transformation
思路
首先观察到二进制数可以被拆分
所以问题转化成一个 \(01\) 矩阵, 其中你可以对每一行清零, 对每一列设为全 \(1\)
手动模拟样例可以发现, 无解仅当会出现类似于环形结构
我们考虑建图
你发现对于一个确定的点 \((i, j)\) ,
- 如果其值为 \(0\) , 那么 \(i\) 行的清零操作一定在 \(j\) 列的全 \(1\) 操作之后
- 如果其值为 \(1\) , 那么 \(j\) 行的全 \(1\) 操作一定在 \(i\) 行的清零操作之后
我们考虑对于这些点来建图, 如果是 \(\rm{DAG}\) , 那么有解, 否则无解
因为你从一个操作绕一圈依赖回了自己肯定是不行的
需要注意的是连边 \(u \to v\) 的含义是如果有 \(u\) 且有 \(v\) , 那么必定有 \(v\) 在之前, 而非 \(u, v\) 必须都选择
你注意到如果这样做多半是假的, 因为这样跟初始矩阵甚至没有关系了
考虑观察初始矩阵对其有什么影响, 容易发现的是有些行列我们不需要处理, 怎么在图上表示这个条件
需要注意的是即使这个行列我们没有数直接要求处理, 其约束条件依然存在
最后我们只需要对于所有 一定需要操作的 行 / 列 所在的连通块判环即可
考虑复习
很破防的是似乎 \(\textrm{div 2}\) 要做到 \(\rm{E}\) 才能够到 \(\textrm{NOIP 1=}\) , 哈哈哈
哈哈哈
那真的很需要每日一练了
这周想了一下, 可能包括我的父母都认为搞竞赛是一个错误的决定了吧, 哈哈哈
- 定义操作, 要求把 \(a \to b\)
- 逆向思维, 把 \(b\) 拆成 \(a\)
- 用 \(\rm{STL}\) 维护
- 考虑操作是否能构造出单位操作, 在用单位操作的性质去做题 \((\)一般是到了哪里就不能操作了\()\)
- 打印操作方法
- 考虑一种构造策略
- 约束仅存在于操作顺序上
- 考虑对约束顺序建图判环
- 逆向思维, 把 \(b\) 拆成 \(a\)
题意
给定大小为 的矩阵
对 可以进行以下操作任意次任意顺序
- 选择某一行 和一个非负整数 , 将这一行的每个元素用 进行按位与运算
- 选择某一列 和一个非负整数 , 将这一列的每个元素用 进行按位或运算
求是否能将
先简化问题, 每个行列只能与/或一个值, 不影响答案
不存在进位, 把 \(A, B\) 拆成 \(01\) 矩阵处理
那很舒服了, 不难发现与/或一个值也限制在 \(0, 1\) 中了
但是每个行列只能与/或一个值好像会影响答案, 急急急
方法一
不难发现 与运算只考虑 \(1\) , 或运算只考虑 \(0\)
所以尝试构造方式, 先把一个行变成 \(1\) , 然后再用列操作一个一个转化成目标矩阵
什么时候不可以这么做?
对于一个行, 我们可以知道其是否需要行操作, 进而处理出需要哪些列操作
但是你发现不能直接从第 \(1 \sim n\) 行这样模拟, 因为可能先处理其他行是合法的, 也就是说可能出现行的排列方式产生的问题
不妨把格局打开, 一个位置对操作顺序的要求是什么
- \(1 \to 0\)
对当前行的赋 \(1\) 操作应当在对当前列的赋 \(0\) 操作之前 - \(0 \to 1\)
对当前行的赋 \(1\) 操作应当在对当前列的赋 \(0\) 操作之后
因此对每一行列的 \(0, 1\) 操作建边, 出现环则无解
方法二
转化问题变成对于 \(01\) 矩阵, 可以将一行变成 \(0\) 或者将一列变成 \(1\), 判断是否可以得到另一个矩阵
逆向思维, 也就是是否可以通过删除一个矩阵的全 \(0\) 行和全 \(1\) 列使其变为目标矩阵
乱跑
代码
放一份 \(\rm{QCZ}\) 大佬的代码, 好看爱看
#include <bits/stdc++.h>
using namespace std;
int n, m, a[1010][1010], b[1010][1010], vis[4010], instk[1010], flag;
int head[4010], nxt[4010], to[4010], cnte;
void addedge (int u, int v) { to[++cnte] = v, nxt[cnte] = head[u], head[u] = cnte; }
void dfs (int u) {
if (instk[u]) { flag = 1; return ; }
if (vis[u]) return ;
instk[u] = vis[u] = 1;
for (int i = head[u]; i; i = nxt[i]) dfs (to[i]);
instk[u] = 0;
}
void solve () {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) scanf("%d", &a[i][j]);
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) scanf("%d", &b[i][j]);
for (int i = 0; i < 30; i++) {
cnte = flag = 0; for (int i = 0; i <= n + m + 10; i++) head[i] = vis[i] = instk[i] = 0;
for (int x = 1; x <= n; x++) for (int y = 1; y <= m; y++) if (((b[x][y] >> i) & 1) == 1) addedge (x, n + y);
for (int y = 1; y <= m; y++) for (int x = 1; x <= n; x++) if (((b[x][y] >> i) & 1) == 0) addedge (n + y, x);
for (int x = 1; x <= n; x++) for (int y = 1; y <= m; y++) if (((a[x][y] >> i) & 1) != ((b[x][y] >> i) & 1)) {
addedge (0, ((a[x][y] >> i) & 1) ? x : n + y);
}
dfs (0); if (flag) { puts ("No"); return ; }
}
puts ("Yes");
}
signed main () {
int t; scanf("%d", &t);
while (t--) solve ();
return 0;
}
总结
二进制拆分可以将问题简化到 \(01\) 上
一般来说只有操作型问题可以建图, 建图的关键是根据结果倒退怎样操作
超级源点可以解决一类求子图问题
一般来说, 在 \(01\) 问题上, 与运算只考虑 \(1\) , 或运算只考虑 \(0\)
对操作顺序的约束, 往往可以拆分到具体元素上
逆向思维经典

浙公网安备 33010602011771号