题目的主要信息:

  • 一个的棋盘编号呈现螺旋状排列,一开始棋子在1号棋盘格上分数为0
  • 一共q次询问,每次都是1-6数字中的一个,记为j,棋子会按照编号移动相应位数j(号会回到1号),按照公式来写:当前在序号i,移动j步后,下一次的位置是
  • 我们要计算的分数,就是加上所有行号或者列号与当前格子相差小于上述移动位数j的所有格子的编号
  • 得分是与前面累加的,且要对1000000007取模

方法一:暴力解法(超时)

具体做法:
暴力解法分为两部分,一个是构建棋盘,另一个是对每次查询计算分数。
构建棋盘部分:我们参照螺旋矩阵的输出,对其设置上下左右四个边界,每次都从左界到右界,然后降低上界,再从上界到下界,然后减小右界,再从右界到左界,然后增大下界,再从下届到上界,然后扩大左界,再从左到右,如此循环直到录入个数字。录入的数字记录在矩阵encode中,同时用一个长度的数组记录每个编号数字对应的行号列号,数组中保存的是pair变量。这样我们构建了棋盘有了从编号到行号列号的映射,也有了从行号列号到编号的映射。
计算分数部分:我们先按照公式计算下一次的位置,然后遍历其上下邻近的共行,每行的编号加入答案中,然后遍历其左右邻近的列,每列的编号加入答案中。当然,这个过程我们要注意避免重复相加,即记录访问过的位置。还要注意的是检查下标是否使数组越界。
最后每次得分取模后再与上一询问最后的分数相加取模即可。

class Solution {
public:
    int mod = 1000000007;
    //构建棋盘
    void draw_cheseboard(int n, vector<vector<int>>& encode, vector<pair<int,int>>& index){
        int up = 0, down = n - 1, left = 0, right = n - 1; //四个边界
        int num = 1;
        while (num <= n * n) {
            for(int i = left; i <= right; i++){ //从左往右
                encode[up][i] = num;
                index[num] = make_pair(up, i);
                num++;
            }
            up++;
            for(int i = up; i <= down; i++) {//从右上往右下
                encode[i][right] = num;
                index[num] = make_pair(i, right);
                num++;
            }
            right--;
            for(int i = right; i >= left; i--){ //从右下往左下
                encode[down][i] = num;
                index[num] = make_pair(down, i);
                num++;
            }
            down--;
            for(int i = down; i >= up; i--){ //从左下往左上
                encode[i][left] = num;
                index[num] = make_pair(i, left);
                num++;
            }
            left++;
        }
    }
    int cal_socre(int& cur, vector<vector<int>>& encode, vector<pair<int,int>>& index, int num){
        int n = encode.size();
        vector<vector<bool> > vis(n, vector<bool>(n, false)); //记录改点有无被加过
        cur = (cur + num - 1) % (n * n) + 1; //移动后的位置
        int res = 0;
        //遍历与之邻接的这些行
        for(int i = index[cur].first - num + 1; i < index[cur].first + num; i++){
            if(i < 0 || i >= n) //越界的不用管
                continue;
            for(int j = 0; j < n; j++){
                if(!vis[i][j]){
                    res = (res + encode[i][j]) % mod;
                    vis[i][j] = true;
                }
            }
        }
        //遍历与之邻近的这些列
        for(int i = index[cur].second - num + 1; i < index[cur].second + num; i++){
            if(i < 0 || i >= n)  //越界的不用管
                continue;
            for(int j = 0; j < n; j++){
                if(!vis[j][i]){ //没有访问过的才加,避免重复
                    res = (res + encode[j][i]) % mod;
                    vis[j][i] = true;
                }
            }
        }
        return res;
    }
    vector<int> getScores(int n, vector<int>& query) {
        vector<vector<int> > encode(n, vector<int>(n)); //棋盘编号
        vector<pair<int,int> > index(n * n); //由编号到行列号索引
        draw_cheseboard(n, encode, index); //构建棋盘
        vector<int> res(query.size(), 0);
        int cur = 1; //记录当前位置
        for(int i = 0; i < query.size(); i++){//一共q次查询
            res[i] = cal_socre(cur, encode, index, query[i]);
            if(i != 0) //与前面的累加
                res[i] = (res[i] + res[i - 1]) % mod;
        }
        return res;
    }
};

复杂度分析:

  • 时间复杂度:,构建棋盘一共循环次,一共次查询,每次查询对于行需要遍历这么多行,同样的也要遍历这么多列,因此复杂度为
  • 空间复杂度:,不管是一维辅助数组还是二维辅助数组,最大空间都是

方法二:整行整列相加

具体做法:
基于方法一我们做出一些改进。
我们发现在计算得分的时候,每次查询对于行需要遍历这么多行,同样的也要遍历这么多列,每行每列都是的长度,要完整地遍历,很浪费时间,因此我们可以考虑直接在构建棋盘的时候就记录这些行与列的和,计算分数的时候直接将方法一中需要的行和与列和加入答案中即可。当然会重复计算行与列交叉的部分,但是这部分最多不会超过12*12的区域(因为每次的移动步数最大为6),对于特别大的情况,这就是常数而已,每次计算的时候遍历这部分区域,减去它们中的编号就可以,时间上属于常数级。
图片说明

class Solution {
public:
    int mod = 1000000007;
    //构建棋盘并计算行和与列和
    void draw_cheseboard(int n, vector<vector<int>>& encode, vector<pair<int,int>>& index, vector<int>& col_sum, vector<int>& row_sum){
        int up = 0, down = n - 1, left = 0, right = n - 1; //四个边界
        int num = 1;
        while (num <= n * n) {
            for(int i = left; i <= right; i++){ //从左往右
                encode[up][i] = num;
                col_sum[i] = (num + col_sum[i]) % mod; //列和
                row_sum[up] = (row_sum[up] + num) % mod; //行和
                index[num] = make_pair(up, i);
                num++;
            }
            up++;
            for(int i = up; i <= down; i++) {//从右上往右下
                encode[i][right] = num;
                row_sum[i] = (row_sum[i] + num) % mod;  //行和
                col_sum[right] = (col_sum[right] + num) % mod; //列和
                index[num] = make_pair(i, right);
                num++;
            }
            right--;
            for(int i = right; i >= left; i--){ //从右下往左下
                encode[down][i] = num;
                row_sum[down] = (row_sum[down] + num) % mod; //行和
                col_sum[i] = (col_sum[i] + num) % mod; //列和
                index[num] = make_pair(down, i);
                num++;
            }
            down--;
            for(int i = down; i >= up; i--){ //从左下往左上
                encode[i][left] = num;
                row_sum[i] = (row_sum[i] + num) % mod; //行列
                col_sum[left] = (col_sum[left] + num) % mod;  //列和
                index[num] = make_pair(i, left);
                num++;
            }
            left++;
        }
    }
    int cal_socre(int& cur, vector<vector<int>>& encode, vector<pair<int,int>>& index, int num, vector<int>& col_sum, vector<int>& row_sum){
        int n = encode.size();
        cur = (cur + num - 1) % (n * n) + 1; //移动后的位置
        int res = 0;
        //临近行,行和相加
        for(int i = index[cur].first - num + 1; i < index[cur].first + num; i++){
            if(i < 0 || i >= n) //越界的不用管
                continue;
            res = (res + row_sum[i]) % mod;
        }
        //邻近列,列和相加
        for(int i = index[cur].second - num + 1; i < index[cur].second + num; i++){
            if(i < 0 || i >= n)  //越界的不用管
                continue;
            res = (res + col_sum[i]) % mod;
        }
        //找到重复的部分,减掉
        for(int i = index[cur].first - num + 1; i < index[cur].first + num; i++){
            for(int j = index[cur].second - num + 1; j < index[cur].second + num; j++){
                if(i < 0 || i >= n || j < 0 || j >= n)
                    continue;
                res = (res - encode[i][j] + mod) % mod;
            }
        }
        return res;
    }
    vector<int> getScores(int n, vector<int>& query) {
        vector<vector<int> > encode(n, vector<int>(n)); //棋盘编号
        vector<pair<int,int> > index(n * n); //由编号到行列号索引
        vector<int> col_sum(n, 0); //某一列编号和
        vector<int> row_sum(n, 0); //某一行编号和
        draw_cheseboard(n, encode, index, col_sum, row_sum); //构建棋盘
        vector<int> res(query.size(), 0);
        int cur = 1; //记录当前位置
        for(int i = 0; i < query.size(); i++){//一共q次查询
            res[i] = cal_socre(cur, encode, index, query[i], col_sum, row_sum);
            if(i != 0) //与前面的累加
                res[i] = (res[i] + res[i - 1]) % mod;
        }
        return res;
    }
};

复杂度分析:

  • 时间复杂度:,构建棋盘和方法一一样,都是,计算得分部分因为行和列和直接相加,其中的次遍历就去掉了,同时遍历重复区域属于常数级时间,故不写入
  • 空间复杂度:,不管是一维辅助数组还是二维辅助数组,最大空间都是