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

P7515 [省选联考 2021 A 卷] 矩阵游戏
解题报告。
不一定更好的阅读体验

\(\text{Update 2022.9.29}\):经 @foreverchan 提醒,增加了有关做法正确性的证明,并修改了一些语句。


一年前我在省选赛场上折戟沉沙,一年后我从倒下的地方再摔一跤。

我成功了,我仍然是以前的那个我。


神题,希望自己讲得清楚一些。

先不考虑 \(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\) 个未知数。

个人感觉比较好看的代码:

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
*/
posted @ 2022-09-27 17:53  UperFicial  阅读(82)  评论(0编辑  收藏  举报