「THUPC 2021 初赛」区间矩阵乘法

link
a n s = ∑ i ∑ j ∑ k a [ p 1 + d ∗ i + j ] × a [ p 2 + d ∗ j + k ] ( 0 ≤ i , j , k < d ) ans=\sum_i \sum_j \sum_k a[p1+d*i+j]\times a[p2+d*j+k] (0\le i,j,k<d) ans=ijka[p1+di+j]×a[p2+dj+k](0i,j,k<d)

因为这个 k k k 的加 1 1 1 只会导致每次后面那个数的下标加 1 1 1,考虑前缀和。

p r e [ i ] = ∑ a [ k ] ( k < = i ) pre[i]=\sum a[k](k<=i) pre[i]=a[k](k<=i)

∑ i ∑ j a [ p 1 + d ∗ i + j ] ∗ ( p r e [ p 2 + d ∗ j + d − 1 ] − p r e [ p 2 + d ∗ j − 1 ] ) \sum_i \sum_j a[p1+d*i+j]*(pre[p2+d*j+d-1]-pre[p2+d*j-1]) ija[p1+di+j](pre[p2+dj+d1]pre[p2+dj1])

每次 i i i 的变化只会影响一个值,老套路,移一下求和。

∑ j ∗ ( p r e [ p 2 + d ∗ j + d − 1 ] − p r e [ p 2 + d ∗ j − 1 ] ) ∗ ∑ i a [ p 1 + d ∗ i + j ] \sum_j*(pre[p2+d*j+d-1]-pre[p2+d*j-1])*\sum_i a[p1+d*i+j] j(pre[p2+dj+d1]pre[p2+dj1])ia[p1+di+j]

再考虑前缀和,引入 p r e [ i ] [ j ] pre[i][j] pre[i][j] 为公差为 j j j 的前缀和。

∑ j ∗ ( p r e [ p 2 + d ∗ j + d − 1 ] [ 1 ] − p r e [ p 2 + d ∗ j − 1 ] [ 1 ] ) ∗ ( p r e [ p 1 + d ∗ ( d − 1 ) + j ] [ d ] − p r e [ p 1 + j − d ] [ d ] ) \sum_j*(pre[p2+d*j+d-1][1]-pre[p2+d*j-1][1])*(pre[p1+d*(d-1)+j][d]-pre[p1+j-d][d]) j(pre[p2+dj+d1][1]pre[p2+dj1][1])(pre[p1+d(d1)+j][d]pre[p1+jd][d])(注意这里是减 d d d,不是减 1 1 1,因为公差是 d d d

然后算一算时间复杂度: O ( n ln ⁡ n + n ∗ d ) \mathcal {O}(n\ln n+n*d) O(nlnn+nd),乍一看过不了,但题目保证 p r e pre pre 数组的下标不超过 n n n,即 d 2 ≤ n d^2\le n d2n d ≤ n d\le \sqrt n dn

所以时间复杂度为: O ( n ln ⁡ n + n n ) \mathcal {O}(n\ln n+n\sqrt n) O(nlnn+nn )。有点卡常,需要把 p r e pre pre 数组小的一维放在前面,这样可以快一些。另外,对 2 32 2^{32} 232 取模,开个 u n s i g n e d   i n t unsigned\ int unsigned int 就行了。

此题难点:1.想到用公差为 j j j 的前缀和表示。2.发现 d ≤ n d\le \sqrt n dn 。3.卡常。

Code

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <climits>
#define unsigned int
using namespace std;
const int MAXN = 2e5 + 5;
int n, m, a[MAXN], pre[450][MAXN], t, ans;
signed main() {
	int d, p1, p2;
	scanf("%u", &n); t = sqrt(n);
	for(int i = 1; i <= n; i ++) scanf("%u", &a[i]);
	for(int i = 1; i <= t; i ++) for(int j = 1; j <= n; j ++) pre[i][j] = (j - i >= 1 ? pre[i][j - i] : 0) + a[j];
	scanf("%u", &m);
	for(int i = 1; i <= m; i ++) {
		scanf("%u%u%u", &d, &p1, &p2); ans = 0;
		for(int j = 0; j < d; j ++) ans += (pre[1][p2 + d * j + d - 1] - pre[1][p2 + d * j - 1]) * (pre[d][p1 + d * (d - 1) + j] - (p1 + j - d >= 1 ? pre[d][p1 + j - d] : 0));
		printf("%u\n", ans);
	}
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值