题目:
输入样例 |
4 1 100 5 5 5 1 1 2 2 1 2 1 1 2 2 2 3 1 1 4 |
输出样例 |
101 11 11 |
考察知识点:区间信息维护与查询
思路:
套用 线段树 或 分块模板
数据范围 最多开5次平方即变为1
故除了维护区间和之外,可以再维护一下区间的最大值
当整个区间的值都为1或0时,则该区间可以无需操作
代码:
线段树 ( time:916ms )
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 10, M = 1e6 + 10;
int a[M];
struct Node
{
int l, r; // 区间的左端点和右端点
int mx; // 存区间[l,r]的最大值
int sum;
} tr[N * 4]; // 一般开题目数据范围的4倍
void pushup(int u) // 由子节点的信息计算父节点的信息
{
tr[u].mx = max(tr[2 * u].mx, tr[2 * u + 1].mx);
tr[u].sum = tr[2 * u].sum + tr[2 * u + 1].sum;
// 根节点的最大值由左右孩子区间的最大值取一个max
}
void build(int u, int l, int r) // 递归建树
{
tr[u] = {l, r}; // u区间的左右儿子是l,r
if (l == r)
{
tr[u].mx = tr[u].sum = a[l];
return; // 如果已经到叶子结点了,直接退出
}
int mid = l + r >> 1; // 区间一分为二
build(u * 2, l, mid); // 递归建立左子树
build(u * 2 + 1, mid + 1, r); // 递归建立右子树
pushup(u);
}
int query(int u, int L, int R)
{
if (tr[u].l >= L && tr[u].r <= R)
return tr[u].sum;
int mid = tr[u].l + tr[u].r >> 1;
int v = 0;
if (L <= mid)
v += query(u * 2, L, R);
// 如果查询区间的左端点在当前区间中点的左边,那么接着查当前区间的左孩子
if (R > mid)
v += query(u * 2 + 1, L, R);
// 如果查询区间的右端点在当前区间中点的右边,那么接着查当前区间的右孩子
return v;
}
void modify(int u, int L, int R) // 修改操作
{
if (tr[u].mx <= 1)
return;
if (tr[u].l == tr[u].r) // 说明u已经是叶节点,直接把叶节点修改掉
{
tr[u].mx = tr[u].sum = (int)sqrt(tr[u].mx);
return;
}
else
{
int mid = tr[u].l + tr[u].r >> 1;
if (L <= mid)
modify(u * 2, L, R); // 递归
if (R > mid)
modify(u * 2 + 1, L, R);
pushup(u); // 把子节点信息的修改更新到父节点信息
}
}
signed main()
{
int n = 0, last = 0;
scanf("%lld", &n);
for (int i = 1; i <= n; i++)
scanf("%lld", &a[i]);
build(1, 1, n); // 递归建树
int m;
scanf("%lld", &m);
while (m--)
{
int op, l, r;
scanf("%lld%lld%lld", &op, &l, &r);
if (op == 1)
printf("%lld\n", query(1, l, r));
else
modify(1, l, r);
}
return 0;
}
分块 ( time:2820ms )
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 10;
int n, m, a[N], mx[N];
int len, tot, l[N], r[N], belong[N];
// len块的长度 tot块的数量 l[某个块左端点] r[某个块的右端点] belong[某个点属于哪个块]
int add[N]; // 懒标记 表示某个数在哪个区间被加了多少次
int sum[N], s[N];
// sum[]记录每个块的总和 s[]以块为单位的前缀和
void init()
{
len = sqrt(n), tot = (n + len - 1) / len;
// 初始化左右端点
for (int i = 1; i <= tot; i++)
l[i] = r[i - 1] + 1, r[i] = i * len;
r[tot] = n; // 最后一个块有可能长度不一样,单独定右端点
for (int i = 1; i <= tot; i++)
{
for (int j = l[i]; j <= r[i]; j++)
belong[j] = i, sum[i] += a[j], mx[i] = max(mx[i], a[j]); // 维护每个块的最大值
s[i] = s[i - 1] + sum[i];
}
}
// 区间修改
void modify(int ql, int qr)
{
int p = belong[ql], q = belong[qr];
if (p == q)
{
for (int i = ql; i <= qr; i++)
{
int tmp = sqrt(a[i]);
sum[p] += (tmp - a[i]), a[i] = tmp;
}
// 更新整个块的最大值
for (int i = l[p]; i <= r[p]; i++)
mx[p] = max(mx[p], a[i]);
// 更新块的前缀和
for (int i = p; i <= tot; i++)
s[i] = s[i - 1] + sum[i];
return;
}
mx[p] = 0;
for (int i = ql; i <= r[p]; i++)
{
int tmp = sqrt(a[i]);
sum[p] += (tmp - a[i]), a[i] = tmp;
}
for (int i = l[p]; i <= r[p]; i++)
mx[p] = max(mx[p], a[i]);
for (int i = p + 1; i <= q - 1; i++)
{
// 当当前块的最大值为'1'时,即整个块都是'1',平方后整个块的值不变
if (mx[i] <= 1)
continue;
mx[i] = 0;
sum[i] = 0;
for (int j = l[i]; j <= r[i]; j++)
a[j] = sqrt(a[j]), mx[i] = max(mx[i], a[j]), sum[i] += a[j];
}
mx[q] = 0;
for (int i = qr; i >= l[q]; i--)
{
int tmp = sqrt(a[i]);
sum[q] += (tmp - a[i]), a[i] = tmp;
}
for (int i = l[q]; i <= r[q]; i++)
mx[q] = max(mx[q], a[i]);
// 更新前缀和
for (int i = p; i <= tot; i++)
s[i] = s[i - 1] + sum[i];
}
// 查询操作
int query(int ql, int qr)
{
int p = belong[ql], q = belong[qr];
int ans = 0;
if (p == q)
{
for (int i = ql; i <= qr; i++)
ans += a[i] + add[p];
return ans;
}
ans = s[q - 1] - s[p + 1 - 1];
// 查询区间的左端点 到· 最左区间的右端点
for (int i = ql; i <= r[p]; i++)
ans += a[i] + add[p];
// 查询区间的右端点 到· 最右区间的左端点
for (int i = qr; i >= l[q]; i--)
ans += a[i] + add[q];
return ans;
}
signed main()
{
scanf("%lld", &n);
for (int i = 1; i <= n; i++)
scanf("%lld", &a[i]);
init();
scanf("%lld", &m);
int op, l, r;
while (m--)
{
scanf("%lld%lld%lld", &op, &l, &r);
if (op == 2)
modify(l, r);
else
printf("%lld\n", query(l, r));
}
return 0;
}