1 条题解

  • 0
    @ 2024-12-24 1:27:41

    由题意可知这是一道区间修改,最后区间查询的问题。因为只有一次查询,所以我们可以不上线段树或者树状数组,直接用数组做。

    思考这个问题:对每个区间里的数依次加 12,22,32,1^2,2^2,3^2,\cdots,可以类比到我们学差分的时候是对每个数加 xx。二者是否有相同点。

    首先看普通的差分:将 [2,5][2,5] 这个区间的每个数加 11

    0,1,1,1,1,0,0,0,1,1,1,1,0,0,\cdots

    差分一次得:

    0,1,0,0,0,1,0,0,1,0,0,0,-1,0,\cdots

    我们可以看到对差分数组 bb 的影响就是 bl+1,br+11b_l+1,b_{r+1}-1,两次操作

    那么对于每次加的数不是 11

    0,1,4,9,16,0,0,0,0,0,1,4,9,16,0,0,0,0,\cdots

    多次差分:

    $$0,1,3,5,7,-16,0,0,0,\cdots\\ 0,1,2,2,2,-23,16,0,0,\cdots\\ 0,1,1,0,0,-25,39,-16,0,\cdots $$

    我们看到做了三次差分后,变得好像有规律了,影响了 l,l+1,r+1,r+2,r+3l,l+1,r+1,r+2,r+3 这五个地方的值。所以我们每次加的操作可以等价操作这 55 个位置的值。

    那么我们来看看每个位置有什么方法可以快速计算出影响。

    bl+1,bl+1+1b_l+1,b_{l+1}+1 这两个是可以一眼看出的,剩下的三个我们推导一下,我们记区间长度为(len=rl+1len=r-l+1):

    r+1r+1 位置,相当于减去了 len2+2×len+1len^2+2\times len+1

    r+2r+2 位置,相当于加上了 2×len2+2×len12\times len^2 + 2\times len - 1

    r+3r+3 位置,相当于减去了 len2len^2

    所以我们每次就修改这 55 个位置,最后 33 次前缀和就可以复原数组了,然后在一次前缀和计算数组的和。注意取模计算减法!!!

    参考代码:

    #include <bits/stdc++.h>
    
    using u32 = unsigned;
    using i64 = long long;
    using u64 = unsigned long long;
    using u128 = unsigned __int128;
    
    constexpr int mod = 998244353, N = 200010;
    
    std::vector<i64> a(N);
    
    void getsum(int n) {
    	for (int i = 1; i <= n; i++) {
    		a[i] = (a[i] + a[i - 1]) % mod;
    	}
    }
    
    int main() {
    	std::ios::sync_with_stdio(false);
    	std::cin.tie(nullptr);
    
    	int n, m;
    	std::cin >> n >> m;
    	while (m--) {
    		int l, r;
    		std::cin >> l >> r;
    		a[l]++;
    		a[l + 1]++;
    		a[r + 1] = (a[r + 1] % mod + mod - ((i64)(r - l + 1) * (r - l + 1) % mod + 2LL * (r - l) % mod + 3) % mod) % mod;
    		a[r + 2] = (a[r + 2] % mod + 2 * (i64)(r - l + 1) * (r - l + 1) % mod + 2LL * (r - l) % mod + 1) % mod;
    		a[r + 3] = (a[r + 3] % mod + mod - ((i64)(r - l + 1) * (r - l + 1) % mod)) % mod;
    	}
    
    	getsum(n);
    	getsum(n);
    	getsum(n);
    	getsum(n);
    
    	std::cout << a[n] % mod << "\n";
    	
    	return 0;
    }
    

    方法二:将区间修改改为单点修改。

    其实我们可以直接将这个区间里需要增加的值直接计算出来,通过平方和公式:

    12+22+32++n2=n(n+1)(2n+1)61^2+2^2+3^2+\cdots+n^2=\frac{n(n+1)(2n+1)}{6}

    这样我们可以直接将增加的值直接加到 rr 的位置,在 r+1r+1 的位置减去。只需要执行 22 次操作,然后前缀和一次还原。

    注意:在取模运算做除法运算时,需要将除法变为乘法,即乘以这个数在 mod 下的乘法逆元。

    参考代码:

    #include <bits/stdc++.h>
    
    using u32 = unsigned;
    using i64 = long long;
    using u64 = unsigned long long;
    using u128 = unsigned __int128;
    
    constexpr int mod = 998244353, N = 200010;
    
    std::vector<i64> a(N);
    
    i64 power(i64 a, i64 b) {
    	i64 res = 1;
    	while (b) {
    		if (b & 1) res = res * a % mod;
    		a = a * a % mod;
    		b >>= 1;
    	}
    	return res;
    }
    
    i64 inv(i64 x) {
    	return power(x, mod - 2);
    }
    
    void getsum(int n) {
    	for (int i = 1; i <= n; i++) {
    		a[i] = (a[i] + a[i - 1]) % mod;
    	}
    }
    
    int main() {
    	std::ios::sync_with_stdio(false);
    	std::cin.tie(nullptr);
    
    	int n, m;
    	std::cin >> n >> m;
    	while (m--) {
    		int l, r;
    		std::cin >> l >> r;
    		i64 len = r - l + 1;
    		i64 add = (len * (len + 1) % mod * (len * 2 + 1) % mod * inv(6)) % mod;
    		a[r] = (a[r] + add) % mod;
    		a[r + 1] = (a[r + 1] % mod + mod - add) % mod;
    	}
    
    	getsum(n);
    	getsum(n);
    
    	std::cout << a[n] % mod << "\n";
    	
    	return 0;
    }
    

    信息

    ID
    154
    时间
    2000ms
    内存
    256MiB
    难度
    8
    标签
    递交数
    25
    已通过
    6
    上传者