题意:有一个长度为L(1 <= L<= 10)的数组A和一个无限大的矩阵M。执行下面的代码:
int cursor = 0;
for (int i = 0; ; ++i) { for (int j = 0; j <= i; ++j) { M[j][i - j] = A[cursor]; cursor = (cursor + 1) % L; } }
然后是Q次询问,每次输入两个点(x0,y0)(左上),(x1,y1)(右下),求子矩阵内的数字之和。
思路:可以看出M的填数顺序是这样的:
1 | 2 | 4 | 7 | ... |
3 | 5 | 8 | ... | |
6 | 9 | ... | ||
10 | ... | |||
... |
推导一番可得:
M[i, j] = A[((i + j) * (i + j + 1) / 2 + i) % L] = A[((i * i + j * j + i + j) / 2 + i * j + i) % L]
M[i + 2L, j] = A[((i + j + 2 * L) * (i + j + 1 + 2 * L) / 2 + i + 2 * L) % L] = A[((i * i + j * j + i + j) / 2 + i * j + i + 2 * (i + j + L + 1) * L) % L] = A[((i * i + j * j + i + j) / 2 + i * j + i) % L] = M[i, j]
同理,M[i, j + 2 * L] = M[i, j].
可以看出,整个矩阵都以2L* 2L 的小矩阵为循环节,打表求出循环节即可。
qk[i, j] 记录子矩阵[0, 0] 到 [i, j] 的元素之和。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <stack>
#include <cmath>
#include <list>
#include <cstdlib>
#include <set>
#include <string>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int maxn = 21;
int a[11], L;
ll qk[41][41];
ll calc(int n, int m) {
if (n < 0 || m < 0) {
return 0ll;
}
return qk[L - 1][L - 1] * (n / L) * (m / L) // 被完整覆盖的小矩阵
+ qk[L - 1][m % L] * (n / L) // 右侧多余的小矩阵
+ qk[n % L][L - 1] * (m / L) // 下边多余的小矩阵
+ qk[n % L][m % L]; // 右下角多余的部分
}
int main(){
int t, q, x0, x1, y0, y1;
scanf("%d", &t);
while (t--) {
scanf("%d", &L);
for (int i = 0; i < L; ++i) {
scanf("%d", &a[i]);
}
int k = 0;
for (int i = 0; i < 4 * L; ++i) { //要求出完整的2L*2L的矩阵,i要循环到4L
for (int j = 0; j <= i; ++j) {
qk[j][i - j] = (ll)a[k];
k = (k + 1) % L;
}
}
L *= 2;
for (int i = 0; i < L; ++i) { //二维前缀和
for (int j = 0; j < L; ++j) {
if (i) {
qk[i][j] += qk[i - 1][j];
}
if (j) {
qk[i][j] += qk[i][j - 1];
}
if (i && j) {
qk[i][j] -= qk[i - 1][j - 1]; //减去重复加的部分
}
}
}
scanf("%d", &q);
while (q--) {
scanf("%d%d%d%d", &x0, &y0, &x1, &y1);
printf("%lld\n", calc(x1, y1) - calc(x1, y0 - 1) - calc(x0 - 1, y1) + calc(x0 - 1, y0 - 1)); //矩阵运算
}
}
return 0;
}