题目的主要信息:
- 一个
的棋盘编号呈现螺旋状排列,一开始棋子在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;
}
};复杂度分析:
- 时间复杂度:
,构建棋盘和方法一一样,都是
,计算得分部分因为行和列和直接相加,其中的
次遍历就去掉了,同时遍历重复区域属于常数级时间,故不写入
- 空间复杂度:
,不管是一维辅助数组还是二维辅助数组,最大空间都是

京公网安备 11010502036488号