OMG_wc
2020-08-13 15:39:09
写一个比较自然的题解,不需要利用什么性质。分别给出了用树状数组、线段树实现的方法,时间复杂度皆为
由数据范围
对当前选中的数
移项得到
为了方便叙述,记
然后观察对某个选中的数
举个例子:
这样如果我们能用某种方法把每段里的数同时处理,那么总共需要处理的段数就是
具体来说,假设当前这段的等差数列是
并且可以发现同一段是不会有任何贡献的(因为是递减的),只需查询前面所有段的
记
现在问题简化为区间加一个数和求二阶前缀和,下面分别用树状数组和线段树解决这个问题:
想必你用树状数组做过 “区间加一个数,区间求和”的问题,本质单点更新,是求二阶前缀和。
事实上,使用
比如
设
对这题而言,现在区间更新可以转换为差分数组
那么只要用三个树状数组维护
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF = 0x3f3f3f3f;
const LL mod = 1e9 + 7;
const int N = 500005;
// 修改差分 来维护前缀和的前缀和
// c1 为差分d c2为d*i c3 为d*i*i
LL c1[N * 2], c2[N * 2], c3[N * 2];
LL sum(int x) {
LL res = 0;
for (int i = x; i > 0; i -= i & -i) {
res += c1[i] * (x + 2) * (x + 1) - c2[i] * (2 * x + 3) + c3[i];
}
return res / 2;
}
void add(int x, LL d, int n) {
for (int i = x; i <= n; i += i & -i) {
c1[i] += d;
c2[i] += d * x;
c3[i] += d * x * x;
}
}
int a[N];
vector<int> b[N];
int main() {
int n;
scanf("%d%*d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
b[a[i]].push_back(i);
}
const int wc = n + 1; // 偏移量,把[-n,n] 平移到 [1,2n+1]
LL ans = 0;
for (int i = 0; i < n; i++) {
b[i].push_back(n + 1);
int last = 0;
for (int j = 0; j < b[i].size(); j++) {
int y = 2 * j - last + wc, x = 2 * j - (b[i][j] - 1) + wc;
// 查询 sum([1,t-1] 的权值和), 其中t在[x,y]范围内,
ans += sum(y - 1) - (x >= 3 ? sum(x - 2) : 0);
// [x,y] 这些数的权值+1
add(x, 1, 2 * n + 1);
add(y + 1, -1, 2 * n + 1);
last = b[i][j];
}
last = 0;
for (int j = 0; j < b[i].size(); j++) {
int y = 2 * j - last + wc, x = 2 * j - (b[i][j] - 1) + wc;
add(x, -1, 2 * n + 1);
add(y + 1, 1, 2 * n + 1);
last = b[i][j];
}
}
printf("%lld\n", ans);
return 0;
}
其实也可以用两棵线段树来求二阶前缀和,但这就没有意思了,和树状数组维护三阶前缀和没区别。
我的方法是用线段树维护权值的前缀和
那么线段树怎么维护等差数列呢?
我们可以发现任意个等差数列的和都是等差数列,只是公差和首项发生了变化。
那么只要把公差和首项作为延迟标记,来维护区间和就好了,具体实现见代码。
因为维护的是
完整代码如下:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF = 0x3f3f3f3f;
const LL mod = 1e9 + 7;
const int N = 500005;
// sx,gc 表示首项和公差
LL val[N << 3], sx[N << 3], gc[N << 3];
inline void push_up(int u) {
val[u] = val[u << 1] + val[u << 1 | 1];
}
inline void gx(int u, LL v, LL d, int len) {
val[u] += (v + v + (len - 1) * d) * len / 2;
sx[u] += v;
gc[u] += d;
}
inline void push_down(int u, int len1, int len2) {
if (sx[u] || gc[u]) {
gx(u << 1, sx[u], gc[u], len1);
gx(u << 1 | 1, sx[u] + gc[u] * len1, gc[u], len2);
sx[u] = gc[u] = 0;
}
}
void update(int u, int l, int r, int x, int y, int v, int d) {
if (x <= l && r <= y) {
gx(u, v + (l - x) * d, d, r - l + 1);
return;
}
int mid = l + r >> 1;
push_down(u, mid - l + 1, r - mid);
if (x <= mid) update(u << 1, l, mid, x, y, v, d);
if (y > mid) update(u << 1 | 1, mid + 1, r, x, y, v, d);
push_up(u);
}
LL query(int u, int l, int r, int x, int y) {
if (x <= l && r <= y) {
return val[u];
}
int mid = l + r >> 1;
push_down(u, mid - l + 1, r - mid);
LL res = 0;
if (x <= mid) res += query(u << 1, l, mid, x, y);
if (y > mid) res += query(u << 1 | 1, mid + 1, r, x, y);
return res;
}
int a[N];
vector<int> b[N];
int main() {
int n;
scanf("%d%*d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
b[a[i]].push_back(i);
}
const int wc = n + 1; // 偏移量,把[-n,n] 平移到 [1,2n+1]
LL ans = 0;
for (int i = 0; i < n; i++) {
b[i].push_back(n + 1);
int last = 0;
for (int j = 0; j < b[i].size(); j++) {
int y = 2 * j - last + wc, x = 2 * j - (b[i][j] - 1) + wc;
// 查询 sum([1,t-1] 的权值和), 其中t在[x,y]范围内
ans += query(1, 1, 2 * n + 1, max(x - 1, 1), y - 1);
// [x,y] 这些数的权值+1,真正要维护的是它们的前缀和,
// 在[x,y] 上是加上一段等差数列 1,2,3,...,y-x+1,[y+1,2n+1] 上每个数+y-x+1
update(1, 1, 2 * n + 1, x, y, 1, 1);
if (x + 1 <= 2 * n + 1) update(1, 1, 2 * n + 1, y + 1, 2 * n + 1, y - x + 1, 0);
last = b[i][j];
}
last = 0;
for (int j = 0; j < b[i].size(); j++) {
int y = 2 * j - last + wc, x = 2 * j - (b[i][j] - 1) + wc;
update(1, 1, 2 * n + 1, x, y, -1, -1);
if (x + 1 <= 2 * n + 1) update(1, 1, 2 * n + 1, y + 1, 2 * n + 1, -(y - x + 1), 0);
last = b[i][j];
}
}
printf("%lld\n", ans);
return 0;
}
最后,同样时间复杂度是