上交三月月赛[SJTU] 1102 Candles
题目大意:
一个01方阵,初始状态全1,每次操作把一个到主对角线为止的L形的线上的格子改变状态,求最少操作几次
思路:
每个格子可能被多次改变。显然的也是唯一关键的一点就是注意到能对某个格子改变的只有对该格子上方和右方的格子的操作。
所以说按从右上角/左下角往主对角线的顺序每个格子就可以确定是否要做操作。
然后由于时间复杂度的要求,用up数组记录该格以及该格上方格子所做的操作数的总和,用rt数组记录右方。如此可以O(1)的转移。上方和右方的操作数总和为up[x-1][y]+rt[x][y+1]。如果把1做了如此次的改变后和目标不符,就要对当前格进行改变(操作)。
代码:
#include "iostream" #include "cstdio" #include "cstring" #include "algorithm" using namespace std; #define MAXN 1042 int A[MAXN][MAXN]; // 目标状态 int up[MAXN][MAXN],rt[MAXN][MAXN]; // up/right int line[MAXN]; int n,ans; void trans() { for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) A[i][j] = A[j][i]; } int readch() { char c = getchar(); while(!(c=='1' || c=='0')) c = getchar(); return c-'0'; } void deal() { for(int yy = n;yy >= 1;yy--) { int x = 1;int y = yy; //x,y为从右上开始沿斜线移动的指针 while(y <= n) { bool change = 0; //判断x,y这格是否要改变状态 if( (A[x][y] + up[x-1][y] + rt[x][y+1]) % 2 == 0 ) change = 1; ans += change; //更新总计数 up[x][y] = up[x-1][y] + change; rt[x][y] = rt[x][y+1] + change; //更新up,rt数组到当前格; x++;y++; } } } int main() { while(scanf("%d",&n) == 1) { for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) { A[i][j] = readch(); //读入目标状态;为了方便边界,下标从1开始 } for(int i=0;i<=n+1;i++) for(int j=0;j<=n+1;j++) {up[i][j] = 0;rt[i][j] = 0;} //初始化;注意对0..n+1的都要置零 ans = 0; deal(); //对右上的一半处理 for(int i=1;i<=n;i++) line[i] = up[i-1][i] + rt[i][i+1]; //line[i]记录了处理完右上后[i,i]的改变次数 trans(); //转置A数组 deal(); //相当于用同样的方法处理左下;虽然这样写比较丑但方便编写和调试 for(int i=1;i<=n;i++) line[i] += up[i-1][i] + rt[i][i+1]; //得到对角线的两边总的改变次数 for(int i=1;i<=n;i++) if((A[i][i]+line[i])%2 == 0) ans++; //注意为什么对对角线单独处理是因为对角线并不和其它格子等同因为change要算两次而ans只算一次;自然可以小改下deal()也行,但这样减少了调试的清晰,也增加了出错可能 printf("%d\n", ans); } return 0; }