[HNOI2010] 矩阵 matrix
标签:dfs+剪枝。
题解:
这道题看着就像一道dfs题目,没有什么算法可以用来算这个东西,于是想想暴搜。
如果我们确定因为是2*2的子矩阵的和,如果确定了其中三个,那么就可以确定第四个,发现如果确定了第一行和第一列的话,就可以确定整个矩阵了,于是我们枚举只有399个了。
因为要求字典序最小,我们先默认第一行和第一列全部是0,求出一个矩阵。我们先搜索第一行,从左到右。发现在(1,1)位置的数+k,那么在除了第一行和第一列的矩阵中,要合法,就要i+j为偶数的-=k,i+j为奇数的+=k即可。同样在(1,j)位置+=k,那么只影响这一列,便偶数行号-=k,奇数行号+=k即可。我们在保证了第一行最小的情况下,只要保证第一列最小即可满足字典序最小,因为确定第一行和第一列可以唯一的确定一个矩阵。
第一行我们暴搜,当然需要剪枝了,对于每搜到第一行的一个数,就要扫一遍这一列,并且更新这一列的每个元素的所在行的行首的范围,也就是矩阵第一列每一个元素的范围,因为矩阵中的每一个元素只被三个数影响,那就是(1,1),以及行首与列首。更新了第一列每一个元素的范围之后,如果冲突即(L>R)那么就返回0。
这里有一个技巧:按道理范围只要开一个O(n)的数组即可,但是这样需要备份,因为如过搜索失败了,那么回溯也要恢复范围数组的值,很麻烦,那么我们直接开一个二维数组,每次更新时取上一列的值与当前值比较之后再更新,这样就没有回溯的问题了,因为我们是从左到右枚举第一行的。
1 #include<cstdio> 2 #include<iostream> 3 #include<algorithm> 4 using namespace std; 5 const int MAXN=205,INF=0x3f3f3f3f; 6 int n,m,P; 7 int L[MAXN][MAXN],R[MAXN][MAXN],A[MAXN][MAXN]; 8 inline int gi(){int res; scanf("%d",&res); return res;} 9 int F(int x){return (x&1)?1:-1;} 10 int cal(int x,int y) { return A[x][y]+F(x+y)*A[1][1]+F(y)*L[x][m]+F(x)*A[1][y]; } 11 bool dfs(int y) 12 { 13 if(y>m)return 1; 14 int bacl[MAXN]={0},bacr[MAXN]={0}; 15 for(A[1][y]=0;A[1][y]<P;A[1][y]++) 16 { 17 bool flag=1; 18 for(int i=2;i<=n;i++) 19 { 20 int tl=(A[i][y] + A[1][1]*F(i+y) + A[1][y]*F(i)) * F(y+1); 21 int tr=(A[i][y] + A[1][1]*F(i+y) + A[1][y]*F(i)-(P-1)) * F(y+1); 22 if(tl>tr)swap(tl,tr); 23 L[i][y]=max(L[i][y-1],tl); 24 R[i][y]=min(R[i][y-1],tr); 25 if(L[i][y]>R[i][y]) 26 {flag=0; break;} 27 } 28 if(flag) 29 if(dfs(y+1)) 30 return 1; 31 } 32 return 0; 33 } 34 int main() 35 { 36 n=gi(); m=gi(); P=gi(); 37 for(int i=1;i<=n;i++) 38 { 39 for(int j=1;j<=m;j++) 40 { 41 A[i][j]=gi(); 42 if(i!=1 && j!=1) 43 A[i][j]-=(A[i][j-1]+A[i-1][j]+A[i-1][j-1]); 44 R[i][j]=P-1; 45 } 46 } 47 for(;A[1][1]<P;A[1][1]++) 48 { 49 if(dfs(2)) 50 { 51 for(int i=1;i<=n;i++) 52 for(int j=1;j<=m;j++) 53 { 54 if(j==1 && i>1) 55 printf("%d ",L[i][m]); 56 else if(i==1 && j>1) 57 printf("%d%c",A[1][j],j==m?'\n':' '); 58 else 59 printf("%d%c",cal(i,j),j==m?'\n':' '); 60 } 61 return 0; 62 } 63 } 64 return 0; 65 }