QBXT 求和
Description
给定两个长度为n的数列,有两种操作
0 x y z表示将a数组第x个元素修改为y,b数组第x个元素修改为z
1 l r 表示询问
Input
第一行两个正整数n,m
第二行n个数 a1,a2,…,an
第三行n个数 b1,b2,…,bn
接下来m行,表示m次操作
Output
- 对于每个询问操作,输出答案。
Sample Input
5 3
1 2 3 4 5
1 2 3 4 5
1 2 4
0 3 6 6
1 2 4
Sample Output
26 44
Data Size
30% n,m<=100
50% n,m<=4000
另20% 没有修改操作
另20% 保证 ai=bi
100% n,m<=100000
保证1<=ai,bi<=10000
题解:
- 线段树。
- 首先考虑暴力。50pts直接线段树暴力。考虑后20%数据ai = bi的情况。对于这种情况,如果要求区间[l, r],答案是:( sigma[l到r之和] ^ 2 - sigma[l到r的平方和] ) / 2。
- 为什么?初中数学告诉我们:(a + b + c + d + ...) ^ 2 = a ^ 2 + b ^ 2 + c ^ 2 + d ^ 2 + ... + 2(ab + ac + ad + bc + bd + cd + ...)。
- 那么我们要求的就是ab + ac + ad + bc + bd + cd + ...嘛,于是开两个线段树分别维护sigma区间和 and sigma区间平方和即可。
- 考虑正解。
- 这题正解还挺妙的,巧妙利用了线段树的本质。
- 对于一个区间[l, r],算出mid = (l + r) / 2,所求就是[l, r]的答案。
- 因为:l <= i < j <= r;所以:对于点对(i, j)有3种情况。
- 1 <= i < j <= mid
- mid < i < j <= r
- 1 <= i <= mid, mid < j <= r
- 那么显然ans = 情况1的ans + 情况2的ans + 情况3的ans。
- 建立线段树。有t[p].l(区间左边界)、t[p].r(区间右边界)、t[p].sum(所求区间的答案)、t[p].v1(a数组中对应区间之和)、t[p].v2(b数组中对应区间之和)。
- 因为:t[p].sum = 情况1的ans + 情况2的ans + 情况3的ans
- 情况1的ans = t[p << 1].sum;情况2的ans = t[p << 1 | 1].sum
- 情况3我举个例子,你们来理解:
a1 a2 a3 | a4 a5 a6
b1 b2 b3 | b4 b5 b6
'|' 为mid
- 这时情况3的ans = a1b4 + a1b5 + a1b6 + a2b4 + a2b5 + a3b6 = (a1 + a2 + a3) * (b4 + b5 + b6)。
所以一般性的情况下,情况3的ans = t[p << 1].v1 * t[p << 1 | 1].v2
综上所述,这一题主要核心就是在pushup操作的改变,下面给出pushup的代码:
t[p].v1 = t[p << 1].v1 + t[p << 1 | 1].v1;
t[p].v2 = t[p << 1].v2 + t[p << 1 | 1].v2;
t[p].sum = t[p << 1].sum + t[p << 1 | 1].sum + t[p << 1].v1 * t[p << 1 | 1].v2;
- 最后提醒一下,由于sum的转移并不是直接累加,所以查询操作的时候不能直接返回ask(...) + ask(...)。
#include <iostream>
#include <cstdio>
#define N 100005
#define int long long
using namespace std;
struct T {int l, r, v1, v2, sum;} t[N * 4];
int n, m;
int a[N], b[N];
int read()
{
int x = 0; char c = getchar();
while(c < '0' || c > '9') c = getchar();
while(c >= '0' && c <= '9') {x = x * 10 + c - '0'; c = getchar();}
return x;
}
void up(int p)
{
t[p].v1 = t[p << 1].v1 + t[p << 1 | 1].v1;
t[p].v2 = t[p << 1].v2 + t[p << 1 | 1].v2;
t[p].sum = t[p << 1].sum + t[p << 1 | 1].sum + t[p << 1].v1 * t[p << 1 | 1].v2;
}
void build(int p, int l, int r)
{
t[p].l = l, t[p].r = r;
if(l == r) {t[p].v1 = a[l], t[p].v2 = b[l]; return;}
int mid = (l + r) >> 1;
build(p << 1, l, mid), build(p << 1 | 1, mid + 1, r);
up(p);
}
void upd(int p, int l, int r, int v1, int v2)
{
if(t[p].l >= l && t[p].r <= r)
{t[p].v1 = v1, t[p].v2 = v2, t[p].sum = 0; return;}
int mid = (t[p].l + t[p].r) >> 1;
if(l <= mid) upd(p << 1, l, r, v1, v2);
if(r > mid) upd(p << 1 | 1, l, r, v1, v2);
up(p);
}
int reAsk(int p, int l, int r, int op)
{
if(t[p].l >= l && t[p].r <= r)
{
if(op == 1) return t[p].v1;
else return t[p].v2;
}
int mid = (t[p].l + t[p].r) >> 1, ans = 0;
if(l <= mid) ans += reAsk(p << 1, l, r, op);
if(r > mid) ans += reAsk(p << 1 | 1, l, r, op);
return ans;
}
int ask(int p, int l, int r)
{
if(t[p].l >= l && t[p].r <= r) return t[p].sum;
int mid = (t[p].l + t[p].r) >> 1;
if(r <= mid) return ask(p << 1, l, r);
if(l > mid) return ask(p << 1 | 1, l, r);
return ask(p << 1, l, mid) + ask(p << 1 | 1, mid + 1, r) +
reAsk(p << 1, l, r, 1) * reAsk(p << 1 | 1, l, r, 2);
}
signed main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++) a[i] = read();
for(int i = 1; i <= n; i++) b[i] = read();
build(1, 1, n);
for(int i = 1; i <= m; i++)
{
int op = read();
if(!op)
{
int pos = read(), v1 = read(), v2 = read();
upd(1, pos, pos, v1, v2);
}
else
{
int l = read(), r = read();
printf("%lld\n", ask(1, l, r));
}
}
return 0;
}