题解【P7515 [省选联考 2021 A 卷] 矩阵游戏】

· · 题解

P7515 [省选联考 2021 A 卷] 矩阵游戏

解题报告。

不一定更好的阅读体验

--- 一年前我在省选赛场上折戟沉沙,一年后我从倒下的地方再摔一跤。 我成功了,我仍然是以前的那个我。 ---- 神题,希望自己讲得清楚一些。 先不考虑 $0\le a_{i,j}\le 10^6$ 的限制,只考虑 $b_{i,j}=a_{i,j}+a_{i+1,j}+a_{i,j+1}+a_{i+1,j+1}$,瞎鸡巴填一手,得到矩阵 $A$,之后比较自然的想法就是通过 $+1$ 和 $-1$ 来微调这个东西。 其实这个时候差不多就可以想到差分约束了,因为我们最终希望把每一个 $a_{i,j}$ 变成 $0\le a_{i,j}\le 10^6$ 这样一个范围的形式。不过我知道你很急,但你先别急。继续往下做,看看这个想法有没有希望。 两个比较显然的性质: 1. 对于**每一行**轮流进行 $+1$ 和 $-1$ 的操作,结果不变。 2. 对于**每一列**轮流进行 $+1$ 和 $-1$ 的操作,结果不变。 因为任意一个 $2\times 2$ 的矩阵中的一组 $+1$ 和 $-1$ 会相互抵消掉。 我们设 $1$ 操作在第 $i$ 行进行了 $c_{i}$ 次,设 $2$ 操作在第 $j$ 行进行了 $d_i$ 次,那么可以得到如下的矩阵(每一项省略了 $a_{i,j}$): $$ \begin{bmatrix} c_1+d_1 & -c_1+d_2 & \cdots & (-1)^{m+1}c_1+d_n\\ c_2-d_1 & -c_2-d_2 & \cdots & (-1)^{m+1}c_2-d_n\\ \cdots & \cdots & \ddots & \cdots\\ c_n+(-1)^{n+1}d_m & -c_n+(-1)^{n+1}d_m & \cdots & (-1)^{m+1}c_n+(-1)^{n+1}d_m \end{bmatrix} $$ 第一眼:哇这不是差分约束板子吗! 第二眼:我是不是应该发明一个叫 $0\le a_{i,j}+c+d\le 10^6$ 的和分约束? 但是这个和分约束显然可以通过变化 $c$ 的符号来转化成差分约束。 我们将偶数行的 $c$ 变成相反数,奇数列的 $d$ 变成相反数。 那么原矩阵就变成了: $$ \begin{bmatrix} c_1-d_1 & d_2-c_1 & \cdots \\ d_1-c_2 & c_2-d_2 & \cdots\\ \cdots & \cdots & \ddots\\ \end{bmatrix} $$ 这里仅举例 $2\times 2$ 的矩阵已经足够证明正确性,因为包含了所有下标的奇偶性,这样的话转化就做完了。 我们先将第最后一行钦定为 $0$,然后得到了一个不考虑限制的矩阵 $A$,之后按照上面的矩阵,以 $(1,1)$ 为例,得到差分约束 $0\le a_{1,1}+c_1-d_1\le 10^6$ 的形式,跑 $\text{SPFA}$ 就行了。 注意,这道题可以不用建立虚点的,因为这张图已经保证联通了,而且是差不多是一张完全图,用链式前向星容易被卡,而 $\texttt{vector}$ 存图在稠密图的情况下是表现良好的。 证明一下这个做法的正确性,钦定 $a_{i,j}$ 会有 $(n-1)(m-1)$ 个方程,差分约束会建立 $2nm$ 个方程,但这里的方程每有两个会得到 $(n+m)$ 个未知数,每个未知数会得到一个 $a_{i,j}$,所以本质是有 $(n+m)$ 个方程,最终我们得到 $(nm+1)$ 个有关 $a$ 的方程,足以确定 $nm$ 个未知数。 个人感觉比较好看的代码: ```cpp const int MAXN(310); const int MAXM(610); const int MAX(1e6); int n,m,a[MAXN][MAXN],b[MAXN][MAXN],K; struct node{int to,cost;}; vector<node>G[MAXM]; bool inq[MAXM]; queue<int>q; ll d[MAXM]; int cnt[MAXM]; inline void add_edge(int u,int v,int w) { node res; res.to=v,res.cost=w; G[u].push_back(res); return; } inline bool spfa(int s) { rep(i,1,n+m) d[i]=LLINF,inq[i]=false,cnt[i]=0; d[s]=0; q.push(s); while(!q.empty()) { int u=q.front(); q.pop(); inq[u]=false; rep(i,0,(int)G[u].size()-1) { node e=G[u][i]; if(d[e.to]>d[u]+e.cost) { d[e.to]=d[u]+e.cost; if(!inq[e.to]) { if(++cnt[e.to]>=n+m) return false; inq[e.to]=true,q.push(e.to); } } } } return true; } inline void solve() { n=Fread::read(),m=Fread::read(); rep(i,1,n-1) rep(j,1,m-1) b[i][j]=Fread::read(); rev(i,n-1,1) rev(j,m-1,1) a[i][j]=b[i][j]-a[i+1][j]-a[i][j+1]-a[i+1][j+1];//随便构造一个矩阵 A rep(i,1,n) rep(j,1,m) { if((i+j)&1) add_edge(j+n,i,MAX-a[i][j]),add_edge(i,j+n,a[i][j]); else add_edge(i,j+n,MAX-a[i][j]),add_edge(j+n,i,a[i][j]); } if(!spfa(1)) puts("NO"); else { puts("YES"); rep(i,1,n) { rep(j,1,m) { int now=d[i]-d[j+n]; if((i+j)&1) cout<<a[i][j]+now,putchar('\n'); else cout<<a[i][j]-now,putchar('\n'); } putchar('\n'); } } Clear(a); rep(i,1,n+m) G[i].clear(); return; } int main() { int T=Fread::read(); while(T--) solve(); return 0; } /* Date : 2022/9/27 Author : UperFicial Start coding at : 17:03 finish debugging at : 17:50 */ ```