[省选联考 2021 A 卷] 矩阵游戏
一、题目
二、解法
首先发现整个矩阵其实之和最后一行最后一列(我称之为边角)有关,如果确定了他们整个矩阵就确定了。考虑调整法,我们先让边角全为 \(0\),那么得到的矩阵 \(a\) 很可能是不合法的,我们考虑调整它。
调整有一个原则就是保持 \(a\) 能构造出 \(b\),调整 \(a\) 的单个元素是困难的。考虑调整一整行或者一整列,奇数位置加 \(x\) 偶数位置减 \(x\),这样调整 \(b\) 是不会变的,而且因为这样做可以调整出所有的边角可能情况,而边角的状态和整个矩阵的状态一一对应,所以这种调整方法是充分的。具体来说有下面的不等式,\(r_i,c_j\) 分别表示行和列的调整值:
\[0\leq \pm r_i\pm c_j+a_{i,j}\leq 10^6
\]
解决这个不等式很容易想到差分约束,但是会出现 \(x\leq -y+c\) 之类的不等式,这时候可以拆一个表示负数权值的点出来,然后连成对称图就可以保证某点正点和负点的权值相同,但是要讨论很多情况。
还有一个更简洁的方法是把原图进行二染色,让每种颜色对应一种等式:
\[row\ \ \ \begin{matrix}+&-&+&-\\-&+&-&+\\+&-&+&-\\-&+&-&+\end{matrix}
\]
\[col\ \ \ \begin{matrix}-&+&-&+\\+&-&+&-\\-&+&-&+\\+&-&+&-\end{matrix}
\]
- 若 \(i+j\) 为奇数:\(0\leq r_i-c_j+a_{i,j}\leq 10^6\)
- 若 \(i+j\) 为偶数:\(0\leq -r_i+c_j+a_{i,j}\leq 10^6\)
然后用 \(\tt spfa\) 跑图判负环即可,判负环的好方法:维护到每个点的最短路径长度,如果 \(\geq\) 总点数就一定有负环。
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
const int M = 605;
#define ll long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int T,n,m,cnt[M],vis[M];ll dis[M],a[M][M],b[M][M];
struct node
{
int v;ll c;
};vector<node> g[M];
void link(int u,int v,ll c)
{
g[u].push_back({v,c});
}
void spfa()
{
queue<int> q;
memset(dis,0x3f,sizeof dis);
memset(vis,0,sizeof vis);
memset(cnt,0,sizeof cnt);
dis[1]=cnt[1]=0;vis[1]=1;q.push(1);
while(!q.empty())
{
int u=q.front();q.pop();
vis[u]=0;
for(int i=0;i<g[u].size();i++)
{
node x=g[u][i];
if(dis[x.v]>dis[u]+x.c)
{
dis[x.v]=dis[u]+x.c;
cnt[x.v]=cnt[u]+1;
if(cnt[x.v]>=n+m) {puts("NO");return ;}
if(!vis[x.v]) vis[x.v]=1,q.push(x.v);
}
}
}
puts("YES");
for(int i=1;i<=n;i++,puts(""))
for(int j=1;j<=m;j++)
{
ll t=dis[i]-dis[j+n];
if((i+j)%2)
printf("%lld ",t+a[i][j]);
else
printf("%lld ",-t+a[i][j]);
}
}
void work()
{
memset(a,0,sizeof a);
n=read();m=read();
for(int i=1;i<=n+m;i++)
g[i].clear();
for(int i=1;i<n;i++)
for(int j=1;j<m;j++)
b[i][j]=read();
for(int i=n-1;i>=1;i--)
for(int j=m-1;j>=1;j--)
a[i][j]=b[i][j]-a[i+1][j]
-a[i][j+1]-a[i+1][j+1];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if((i+j)%2)
{
//0<=ri-cj+aij<=1e6
//cj<=ri+aij,ri<=cj+1e6-aij
link(i,j+n,a[i][j]);
link(j+n,i,1e6-a[i][j]);
}
else
{
//0<=-ri+cj+aij<=1e6
//ri<=cj+aij,cj<=ri+1e6-aij
link(j+n,i,a[i][j]);
link(i,j+n,1e6-a[i][j]);
}
}
spfa();
}
signed main()
{
T=read();
while(T--) work();
}