【题目链接】 http://acm.hdu.edu.cn/showproblem.php?pid=5755
【题目大意】
一个n*m由0,1,2组成的矩阵,每次操作可以选取一个方格,使得它加上2之后对3取模,周围的四个方格加上1后对3取模,请你在n*m操作次数内让整个矩阵变成0。输出一种方案。
【题解】
枚举第一行的方式显然是不行的,因为3的30次方显然不是可以承受的范围,考虑如果存在第0行元素,那么这一行的最终状态就是第一行的操作次数,因为每个格子很明显只会由第一行对应的正下方的格子影响,我们在第n行操作次数已知的情况下,可以推得第n+1行的操作次数的情况,因此,我们假设第0行的元素为x1,x2……xm,逐行进行线性方程的变换,最后由于最后一行操作结束后必须使得该行全为0,那么我们可以得到m个线性方程,高斯消元可以解出第一行的操作次数。
注意到3是一个神奇的数字,所以在消元的过程中可以直接让确定行和预消除行的首元素相乘获得倍数关系。此外,系数在模情况下的变换和其在其余符号下的运算也是一样的。因为根据同余模定理,(3-t*x1)%3=(3*x1-t*x1)%3=(3-t)%3,所以系数可以和原数进行一样模运算。
【代码】
#include <cstdio> #include <algorithm> #include <cstring> using namespace std; #define rep(i,n) for(int i=1;i<=n;i++) const int N=35; int ans,n,m,T,a[N][N],p[N][N],f[N][N][N]; int DP(int i,int j,int k){ int t=(f[i][j-1][k]+2*f[i][j][k]+f[i][j+1][k]+f[i-1][j][k])%3; return ((3-t)%3+3)%3; } int GetAns(int i,int j){ int t=(p[i][j-1]+2*p[i][j]+p[i][j+1]+p[i-1][j]+a[i][j])%3; return ((3-t)%3+3)%3; } void Gauss(int n,int m) { int d,i,j,k,h,w=0; for(i=1,j=1;j<m;j++,w=0){ for(k=i;k<=n;k++)if(p[k][j])w=k; if(w){ for(k=j;k<=m;k++)swap(p[i][k],p[w][k]); for(k=i+1;k<=n;k++) if(p[k][j]){ d=p[k][j]*p[i][j]%3; for(h=j;h<=m;h++)p[k][h]=(p[k][h]-d*p[i][h]+6)%3; }i++; }if(i>n)break; }for(j=1;j<=m;j++)f[1][j][j]=0; for(j=i-1;j;j--){ for(k=1;k<m;k++)if(p[j][k])break; for (d=0,h=k+1;h<m;h++)if(f[1][h][h]&&p[j][h])d=(d+f[1][h][h]*p[j][h])%3; f[1][k][k]=p[j][k]*(3-d+p[j][m])%3; }memset(p,0,sizeof(p)); for(j=1;j<=m;j++)p[1][j]=(f[1][j][j]+3)%3; } int main(){ scanf("%d",&T); while(T--){ scanf("%d%d",&n,&m); rep(i,n)rep(j,m)scanf("%d",&a[i][j]); memset(f,0,sizeof(f)); rep(i,m)f[1][i][i]=1; rep(i,n)rep(j,m){ f[i+1][j][m+1]=(3-a[i][j]+DP(i,j,m+1))%3; rep(k,m)f[i+1][j][k]=DP(i,j,k); }memset(p,0,sizeof(p)); rep(i,m){ p[i][m+1]=((3-f[n+1][i][m+1])%3+3)%3; rep(j,m)p[i][j]=f[n+1][i][j]; }Gauss(m,m+1);ans=0; rep(i,m)ans+=p[1][i]; for(int i=2;i<=n;i++)rep(j,m)p[i][j]=GetAns(i-1,j),ans+=p[i][j]; printf("%d ",ans); rep(i,n)rep(j,m)rep(k,p[i][j])printf("%d %d ",i,j); }return 0; }