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

一、题目

点此看题

二、解法

首先发现整个矩阵其实之和最后一行最后一列(我称之为边角)有关,如果确定了他们整个矩阵就确定了。考虑调整法,我们先让边角全为 (0),那么得到的矩阵 (a) 很可能是不合法的,我们考虑调整它。

调整有一个原则就是保持 (a) 能构造出 (b),调整 (a) 的单个元素是困难的。考虑调整一整行或者一整列,奇数位置加 (x) 偶数位置减 (x),这样调整 (b) 是不会变的,而且因为这样做可以调整出所有的边角可能情况,而边角的状态和整个矩阵的状态一一对应,所以这种调整方法是充分的。具体来说有下面的不等式,(r_i,c_j) 分别表示行和列的调整值:

[0leq pm r_ipm c_j+a_{i,j}leq 10^6 ]

解决这个不等式很容易想到差分约束,但是会出现 (xleq -y+c) 之类的不等式,这时候可以拆一个表示负数权值的点出来,然后连成对称图就可以保证某点正点和负点的权值相同,但是要讨论很多情况。

还有一个更简介的方法是把原图进行二染色,让每种颜色对应一种等式:

[row egin{matrix}+&-&+&-\-&+&-&+\+&-&+&-\-&+&-&+end{matrix} ]

[col egin{matrix}-&+&-&+\+&-&+&-\-&+&-&+\+&-&+&-end{matrix} ]

  • (i+j) 为奇数:(0leq r_i-c_j+a_{i,j}leq 10^6)
  • (i+j) 为偶数:(0leq -r_i+c_j+a_{i,j}leq 10^6)

然后用 ( t 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();
}