abc269_f Numbered Checker 题解
Numbered Checker
题意
有一个 \(n\times m\) 的方格矩阵,左上角是 \((1,1)\),右下角是 \((n,m)\),每个方格中都有一个整数,其中 \((x,y)\) 中的数字为:
- 如果 \(x+y\) 是奇数,则 \((x,y)\) 中的数字为 \(0\)。
- 否则,\((x,y)\) 中的数字为 \((x-1)\times m + y\)。
有 \(Q\) 组询问,每组询问给定四个整数 \(a,b,c,d\),求所有满足要求的 \((i,j)\) 中的数字之和:
- \(a \leqslant i \leqslant b\)。
- \(c \leqslant j \leqslant d\)。
答案对 \(998244353\) 取模。
思路
等差数列求和。
对于每组询问
暴力时间复杂度:\(O(n\times m)\),爆炸。
推导壹
就拿第一个样例举例子。
可以很明显地发现,奇数行与偶数行排列虽然不太一样,但两者都是等差数列,且公差为 \(2\)。
经过仔细地推导(不给推导过程,自己推去),我们可以发现:
- 对于 \(a+c\) 为奇数的情况下:
- 第 \(a\) 行的等差数列首项为 \((a,c+1)\),项数为 \(\frac{d-c+1}{2}\)。
- 第 \(a+1\) 行的等差数列首项为 \((a+1,c)\),项数为 \(1+\frac{d-c}{2}\)。
- 对于 \(a+c\) 为偶数的情况下:
- 第 \(a\) 行的等差数列首项为 \((a,c)\),项数为 \(\frac{d-c}{2}+1\)。
- 第 \(a+1\) 行的等差数列首项为 \((a+1,c+1)\),项数为 \(\frac{d-c+1}{2}\)。
(以上除法均需带有向下取整)。
时间复杂度:\(O(n)\),还是不够优秀。
推导贰
可以发现,行与行之间也是等差数列!公差为 \(2m\)。
令当前询问第 \(a\) 行的等差数列之和为 \(sum\),项数为 \(p\);第 \(a+1\) 行的等差数列之和为 \(num\),项数为 \(q\)。
还是不给推导过程,答案就可以变成两个等差数列:
- 第一个:以 \(sum\) 为首项,公差为 \(2\times m\times p\),项数为 \(\frac{b-a}{2}+1\)。
- 第二个:以 \(num\) 为首项,公差为 \(2\times m\times q\),项数为 \(\frac{b-a+1}{2}\)。
求出两个等差数列之和,将其加起来就是答案啦,注意取模细节。
时间复杂度:\(O(1)\)。
复杂度
- 时间:\(O(Q)\)。
- 空间:\(O(1)\)。
Code
点击查看代码
#include <iostream>
using namespace std;
using ll = long long;
const int mod = 998244353;
ll n, m, t, a, b, c, d, p, q, sum, num;
ll id (ll x, ll y) { // 将坐标转数值
return ((x - 1) * m + y) % mod;
}
ll Sum (ll s, ll d, ll n) { // 等差数列求和公式
return (s * n + n * (n - 1) / 2 % mod * d % mod) % mod;
}
int main () {
ios::sync_with_stdio(0), cin.tie(0);
for (cin >> n >> m >> t; t; t--) {
cin >> a >> b >> c >> d;
p = (d - c + 1) / 2, q = (d - c) / 2 + 1;
if ((a + c) % 2) {
sum = Sum(id(a, c + 1), 2, p), num = Sum(id(a + 1, c), 2, q); // 套用公式
} else {
swap(p, q);
sum = Sum(id(a, c), 2, p), num = Sum(id(a + 1, c + 1), 2, q);
}
cout << (Sum(sum, 2 * m * p % mod, (b - a) / 2 + 1) + Sum(num, 2 * m * q % mod, (b - a + 1) / 2)) % mod << '\n'; // 各种取模细节
}
return 0;
}