数列分块入门系列
数列分块入门 1
其实线段树和树状数组都可以做,但是毕竟是在练习分块。
分块,顾名思义把数组分成若干块,这里特指分成 \(\sqrt n\) 块,每块长 \(\sqrt n\)。
分块的修改非常暴力,如果一整块都在要修改的区间里,那就一起打个 tag,否则暴力单点修改。
对于整块的修改,一次最多 \(\sqrt n\) 次,而散块显然只会有一头一尾两块,所以次数也是 \(\sqrt n\) 级别的,总复杂 \(\text O(q\sqrt n)\)。
int a[N], tag[N], len;
void add(int l, int r, int x)
{
while (l <= r && l % len != 1) a[l++] += x;
while (l + len <= r) tag[(l - 1) / len + 1] += x, l += len;
while (l <= r) a[l++] += x;
}
signed main()
{
cin.tie (0), cout.tie (0);
ios :: sync_with_stdio (false);
int n;
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
len = sqrt (n);
for (int i = 1; i <= n; i++)
{
int opt, l, r, c;
cin >> opt >> l >> r >> c;
if (opt == 0) add (l, r, c);
else cout << a[r] + tag[(r - 1) / len + 1] << "\n";
}
return 0;
}
数列分块入门 2 & 教主的魔法*
最经典分块 trick,每一块开一个数组维护 排好序后的样子,区间加时对于整块也是直接打 tag,而散块暴力修改完以后要 重新算一次排好序后的数组。查询时对于散块暴力查询,整块使用二分。记得加上标记。
每次修改是 \(\text O(\sqrt n + \sqrt n\log \sqrt n)\),每次查询是 \(\text O(\sqrt n + \sqrt n\log \sqrt n)\)。
int n;
int a[N], len;
int b[510][510], sz[510], tag[510];
void rebuild(int x)
{
for (int i = (x - 1) * len + 1; i <= min (n, x * len); i++)
b[x][i - (x - 1) * len] = a[i];
sort (b[x] + 1, b[x] + sz[x] + 1);
}
void add(int l, int r, int c)
{
int tl = l, tr = r;
bool flag1 = false, flag2 = false;
while (l % len != 1 && l <= r) a[l++] += c, flag1 = true;
while (l + len <= r) tag[l / len + 1] += c, l += len;
while (l <= r) a[l++] += c, flag2 = true;
if (flag1) rebuild ((tl - 1) / len + 1);
if (flag2) rebuild ((tr - 1) / len + 1);
}
int query(int l, int r, int c)
{
int cnt = 0;
while (l % len != 1 && l <= r)
cnt += ((a[l] + tag[(l - 1) / len + 1]) < c), l++;
while (l + len <= r)
{
int now = (l - 1) / len + 1;
int x = lower_bound (b[now] + 1, b[now] + sz[now] + 1, c - tag[now]) - b[now] - 1;
cnt += x, l += len;
}
while (l <= r)
cnt += ((a[l] + tag[(l - 1) / len + 1]) < c), l++;
return cnt;
}
signed main()
{
cin.tie (0), cout.tie (0);
ios :: sync_with_stdio (false);
cin >> n, len = sqrt (n);
for (int i = 1; i <= n; i++)
{
cin >> a[i];
b[(i - 1) / len + 1][++sz[(i - 1) / len + 1]] = a[i];
}
for (int i = 1; i <= (n - 1) / len + 1; i++)
sort (b[i] + 1, b[i] + sz[i] + 1);
for (int i = 1; i <= n; i++)
{
int op, l, r, c;
cin >> op >> l >> r >> c;
if (!op) add (l, r, c);
else cout << query (l, r, c * c) << "\n";
}
return 0;
}
数列分块入门 3
和上面相同,只是二分完后取小于的中最大的。但是在洛谷上要注意数据范围,打在块上的标记是可以爆 long long 的。
#define pos(x) ((x - 1) / len + 1)
#define xth(x) ((x - 1) % len + 1)
typedef long long ll;
int n, len, sz[M];
ll tag[M], b[M][M], a[N];
void rebuild(int x)
{
for (int i = (x - 1) * len + 1; i <= min (x * len, n); i++)
b[x][xth (i)] = a[i];
sort (b[x] + 1, b[x] + sz[x] + 1);
}
void add(int l, int r, int x)
{
int tl = l, tr = r;
bool fl = false, fr = false;
while (l <= r && xth (l) != 1) a[l++] += x, fl = true;
while (l + len <= r) tag[pos (l)] += x, l += len;
while (l <= r) a[l++] += x, fr = true;
if (fl) rebuild (pos (tl));
if (fr) rebuild (pos (tr));
}
ll query(int l, int r, ll x)
{
ll res = -1e18;
while (l <= r && xth (l) != 1)
{
if (a[l] + tag[pos (l)] < x)
res = max (res, 1ll * a[l] + tag[pos (l)]);
l++;
}
while (l + len <= r)
{
int pl = pos (l);
int p = lower_bound (b[pl] + 1, b[pl] + sz[pl] + 1, x - tag[pl]) - b[pl] - 1;
if (p) res = max (res, 1ll * b[pl][p] + tag[pl]);
l += len;
}
while (l <= r)
{
if (a[l] + tag[pos (l)] < x)
res = max (res, 1ll * a[l] + tag[pos (l)]);
l++;
}
return (res == -1e18) ? -1 : res;
}
signed main()
{
cin.tie (0), cout.tie (0);
ios :: sync_with_stdio (false);
cin >> n;
len = sqrt (n);
for (int i = 1; i <= n; i++)
{
cin >> a[i];
b[pos (i)][xth (i)] = a[i];
}
for (int i = 1; i <= pos (n); i++)
sz[i] = xth (min (i * len, n)),
sort (b[i] + 1, b[i] + sz[i] + 1);
for (int i = 1; i <= n; i++)
{
ll op, l, r, x;
cin >> op >> l >> r >> x;
if (!op) add (l, r, x);
else cout << query (l, r, x) << "\n";
}
return 0;
}
数列分块入门 4
和 1 类似,区间加区间查,给整块加上懒标记即可。
int n, len;
ll a[N], p[N], sum[M], tag[M], sz[M];
void add(int l, int r, int x)
{
while (l <= r && l % len != 1)
sum[p[l]] += x, a[l++] += x;
while (l + len <= r)
tag[p[l]] += x, l += len;
while (l <= r)
sum[p[l]] += x, a[l++] += x;
}
ll query(int l, int r, ll mod)
{
ll res = 0;
while (l <= r && l % len != 1)
res += a[l] + tag[p[l]], l++;
while (l + len <= r)
res += sum[p[l]] + tag[p[l]] * sz[p[l]], l += len;
while (l <= r)
res += a[l] + tag[p[l]], l++;
return (res % mod + mod) % mod;
}
signed main()
{
cin.tie (0), cout.tie (0);
ios :: sync_with_stdio (false);
cin >> n;
len = sqrt (n);
for (int i = 1; i <= n; i++)
cin >> a[i], p[i] = (i - 1) / len + 1,
sum[p[i]] += a[i], sz[p[i]]++;
for (int i = 1; i <= n; i++)
{
ll op, l, r, x;
cin >> op >> l >> r >> x;
if (!op) add (l, r, x);
else cout << query (l, r, x + 1) << "\n";
// for (int j = 1; j <= pos (n); j++)
// cout << sum[j] << " " << tag[j] << "\n";
// cout << "\n";
}
return 0;
}
数列分块入门 5
和著名的花神游历各国是一个道理,开根的次数有上限,对于整块,记录一下开根对这一块还会不会造成更改(是否有数不是 \(0/1\)),不会就直接跳过。
int n, len;
ll a[N], p[N], sum[M];
bool flag[M];
void rebuild(int x)
{
if (!flag[x]) return;
sum[x] = 0, flag[x] = false;
for (int i = (x - 1) * len + 1; i <= min (x * len, n); i++)
a[i] = sqrt (a[i]), sum[x] += a[i], flag[x] |= (a[i] > 1);
}
void sqrt_range(int l, int r)
{
while (l <= r && l % len != 1)
{
if (flag[p[l]])
sum[p[l]] -= a[l], a[l] = sqrt (a[l]), sum[p[l]] += a[l];
l++;
}
while (l + len <= r + 1) rebuild (p[l]), l += len;
while (l <= r)
{
if (flag[p[l]])
sum[p[l]] -= a[l], a[l] = sqrt (a[l]), sum[p[l]] += a[l];
l++;
}
}
ll query(int l, int r)
{
ll res = 0;
while (l <= r && l % len != 1)
res += a[l++];
while (l + len <= r + 1) res += sum[p[l]], l += len;
while (l <= r) res += a[l++];
return res;
}
signed main()
{
cin.tie (0), cout.tie (0);
ios :: sync_with_stdio (false);
cin >> n, len = sqrt (n);
for (int i = 1; i <= n; i++)
cin >> a[i], p[i] = (i - 1) / len + 1,
sum[p[i]] += a[i], flag[p[i]] |= (a[i] > 1);
for (int i = 1; i <= n; i++)
{
int op, l, r;
cin >> op >> l >> r;
if (!op) sqrt_range (l, r);
else cout << query (l, r) << "\n";
}
return 0;
}

浙公网安备 33010602011771号