整体二分学习笔记
整体二分
考虑某种奇怪的问题,询问满足单调性,可以二分。但是不同的mid
需要不同的状态来判定,暴力做代价过高。这时往往要使用整体二分来一起计算。此时可以带修。
整体二分实质上就是最大化询问的共用判定。若当前询问区间对应的答案为 ,我们取其中点 ,然后将数据结构的状态调整到 处,然后判断每个询问答案是大于还是小于等于 。把询问分成两个部分 和 后再重复上次操作,直到 。这样整体二分就省去了数据结构在每一次询问中都要改变的数量,操作次数数量级就从 变为 。
例题 1 Dynamic Rankings
题目描述
题目描述
给定一个含有 个数的序列 ,需要支持两种操作:
Q l r k
表示查询下标在区间 中的第 小的数C x y
表示将 改为输入格式
第一行两个正整数 ,表示序列长度与操作个数。
第二行 个整数,表示 。
接下来 行,每行表示一个操作,都为上述两种中的一个。输出格式
对于每一次询问,输出一行一个整数表示答案。
样例 1#Input
5 3 3 2 1 4 7 Q 1 4 3 C 2 6 Q 2 5 3
样例 1#Output
3 6
数据范围
我们把所有的询问和修改按时间顺序排列后放到一起去整体二分。
先想想如果只有一个询问,那么该怎么二分:显然是二分答案。假设当前二分的是 ,则check
是看 中有多少个数 满足 。
现在我们把询问一次二分的思想放在整体二分中。假设当前二分的是 ,然后对应的修改询问序列是 。我们遍历 中的元素 ,如果 是修改 ,且此时修改中的数 满足 ,就用数据结构将 赋值为 ,表示 处的数 满足条件 。如果 是查询,那么就查询当前状态数据结构中 中的和 ,表示 中有 个数大于等于 。如果 ,说明当前询问答案小于等于 ;否则答案比 大。我们可以用树状数组维护这个过程。然后将操作询问序列分为两半,继续递归下去。这样的时间复杂度就是。
参考代码
#include <bits/stdc++.h>
using namespace std;
static constexpr int Maxn = 300005;
static constexpr int inf = 0x3f3f3f3f;
int n, m, qnum;
int a[Maxn];
int ans[Maxn];
bool isQ[Maxn];
struct Query {
int l, r, k, op, id;
} q[Maxn], q1[Maxn], q2[Maxn];
int bit[Maxn];
inline void add(int x, int v) { for (; x < Maxn; x += x & -x) bit[x] += v; }
inline int ask(int x) { int r = 0; for (; x; x &= x - 1) r += bit[x]; return r; }
void solve(int l, int r, int ql, int qr) {
if (ql > qr) return ;
if (l == r) {
for (int i = ql; i <= qr; ++i)
if (q[i].op == 2) ans[q[i].id] = l;
return ;
}
int mid = l + r >> 1;
int cnt1 = 0, cnt2 = 0;
for (int i = ql; i <= qr; ++i) {
if (q[i].op == 1) {
if (q[i].l <= mid) q1[++cnt1] = q[i], add(q[i].id, q[i].r);
else q2[++cnt2] = q[i];
}
else {
int num = ask(q[i].r) - ask(q[i].l - 1);
if (q[i].k <= num) q1[++cnt1] = q[i];
else q[i].k -= num, q2[++cnt2] = q[i];
}
}
for (int i = 1; i <= cnt1; ++i)
if (q1[i].op == 1) add(q1[i].id, -q1[i].r);
int cnt = ql;
for (int i = 1; i <= cnt1; ++i) q[cnt++] = q1[i];
for (int i = 1; i <= cnt2; ++i) q[cnt++] = q2[i];
solve(l, mid, ql, ql + cnt1 - 1);
solve(mid + 1, r, ql + cnt1, qr);
} // solve
int main(void) {
scanf("%d%d", &n, &m); qnum = 0;
for (int i = 1; i <= n; ++i) {
scanf("%d", a + i);
q[++qnum] = (Query){a[i], 1, 0, 1, i};
}
for (int i = 1; i <= m; ++i) {
char op; int l, r, k;
scanf("\n%c %d%d", &op, &l, &r);
isQ[i] = (op == 'Q');
if (isQ[i]) scanf("%d", &k);
if (!isQ[i]) {
q[++qnum] = (Query){a[l], -1, 0, 1, l};
a[l] = r;
q[++qnum] = (Query){a[l], 1, 0, 1, l};
}
else
q[++qnum] = (Query){l, r, k, 2, i};
}
solve(-inf, inf, 1, qnum);
for (int i = 1; i <= m; ++i)
if (isQ[i]) printf("%d\n", ans[i]);
return 0;
} // main
总结 用整体二分解决操作问题,操作询问序列具有时间顺序,不能随意改变内部顺序。
例题 2 神仙的膜法
题目描述
题目背景
是一个众所周知的神仙。他特别喜欢打怪兽。
题目描述
一个月黑风高的夜晚,和在河边上散步。突然间,发现河边上长着一个奇奇怪怪的树。
这珂树有个节点,第个节点上住着一个编号为怪兽,这个怪兽有滴血。
对于一珂长满怪兽的树,会两种魔法:
对这珂树的一条链上的所有怪兽打出的血量
对这珂树的一珂子树中的所有怪兽打出的血量
其中在每一次魔法中不一定相同。
然而,的魔法会随着周围的环境即时间的变化而变化,这也就是说,并不能随心所欲
为所欲为地使用滥用魔法了。不过呢,拥有预测能力,即他珂以知道时刻时,环境是什么样的,和他的魔法会变成什么样。现在,把他在接下来秒内所要使用的所有个魔法跟说了,要算出每一个怪兽会在接下来的几秒内死亡,或者它们能坚强地活下来。然而,并不会数数,所以她向你寻求帮忙。
输入格式
N M h_1 h_2 ... h_n magic_1 magic_2 ... magic_Q
其中
magic_i
表示使用的魔法,是以下两种形式中的一个:
1 u v x
:表示使用第一种魔法,对所有在这条链上的怪兽打出的血量。
2 u x
:表示使用第二种魔法,对所有在子树内的怪物打出的血量。输出格式
对于每一个怪兽,输出一行:若它还能再的魔法中存活下来,输出
alive
;否则输出一个数,表示在打出前个魔法后,这个怪兽还活着,而打出第个魔法后,这个怪兽就死了。样例 1#Input
5 4 1 3 4 7 8 1 2 1 3 2 4 2 5 1 4 5 1 1 4 5 4 1 3 4 2 1 5 3 2
样例 1#Output
3 2 4 3 alive
数据范围
对于所有数据,满足:
对于第个魔法:
若是第一类,则满足;
若是第二类,则满足.
题后一言
附赠的魔法口诀(子真言):
释迦牟尼 脚绽莲花 菩提达摩 你真伟大 天上天下 唯我独尊 如来佛祖 太上老君 耶稣耶稣快显灵
看到要求每个怪物最早在什么时候死掉,我们想到可以使用整体二分。
整体二分了之后,问题就转化为:树链加,子树加,单点查。显然可以树剖+BIT,不过复杂度是 ,过不了 。冷静一下,发现:所有的查询都在修改后,即这是个静态问题。然后我们就可以树上差分了吧。。。不行,整体二分每一层树上差分的复杂度是 ,不是 ,然后就会被***钻的数据卡掉。于是我们的思路是树剖,然后用类似于虚树的思想,我们保留会被用到的数组的下标,对这些下标进行离散化。于是,整体二分每一层的复杂度就是 ,就能解决了。
按上面思路写完后交一发上去,发现:我怎么TLE了?两个 还跑不过 ?其实是在离散化时,此时存在数组里的有用的下标个数最多是 级别,如果用快速排序(特别是C
中的qsort
),复杂度就会被卡成 ,加上整体二分自带的 之后,甚至比树剖+BIT的 还慢。我们考虑到下标不会超过 ,可以用桶排。但桶排的复杂度又是 ,出现了和之前一样的问题。所以类似"根号分治",可以设一个临界值 ,当数组元素个数大于 时就用桶排,因为此时桶排的复杂度是严格小于等于快排的复杂度;否则就用快排。于是这样子的复杂度就是严格 。
参考代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef long long int64_t;
int _swap_tmp;
#define swap(x, y) ({_swap_tmp = x; x = y; y = _swap_tmp; 0;})
#define Maxn 300005
#define Maxm 300005
#define BUC_SORT_SIZE 2200
#define _I_Buffer_Size (20 << 20)
static char _I_Buffer[_I_Buffer_Size];
char *_I_pos = _I_Buffer;
__attribute__((__always_inline__))__inline int read(void) {
while (*_I_pos < 48) _I_pos++;
int n = *_I_pos++ - '0';
while (*_I_pos > 47) n = n * 10 + (*_I_pos++ - '0');
return n;
} // read
int n, m;
int64_t a[Maxn];
int ans[Maxn];
struct Edge { int to, nxt; } e[Maxn << 1];
int head[Maxn], e_tot;
void clear_graph() { memset(head, 0, sizeof(head)); e_tot = 0; }
void add_edge(int u, int v) {
e[++e_tot] = (struct Edge){ .to = v, .nxt = head[u] };
head[u] = e_tot;
} // add_edge
int dep[Maxn], sz[Maxn], son[Maxn], par[Maxn];
int top[Maxn], dfn[Maxn], ind[Maxn];
void dfs1(int u, int fa, int depth) {
sz[u] = 1, son[u] = -1;
dep[u] = depth, par[u] = fa;
for (int i = head[u], v; i; i = e[i].nxt) {
if ((v = e[i].to) == fa) continue;
dfs1(v, u, depth + 1); sz[u] += sz[v];
if (son[u] == -1 || sz[v] > sz[son[u]]) son[u] = v;
}
} // dfs1
void dfs2(int u, int topv) {
static int index = 0;
top[u] = topv; ind[index] = u; dfn[u] = index++;
if (son[u] != -1) dfs2(son[u], topv);
for (int i = head[u], v; i; i = e[i].nxt)
if ((v = e[i].to) != par[u] && v != son[u]) dfs2(v, v);
} // dfs2
#define CHAIN 1
#define SUBTREE 2
#define OP_TYPE int
struct operation {
OP_TYPE type;
int u, v; int64_t val;
} b[Maxm];
int qry[Maxn];
int ids[Maxn * 20], ids_sz, gid[Maxn];
int64_t dif[Maxn];
__attribute__((__always_inline__)) __inline void add_range(int l, int r, int64_t val) {
dif[gid[l]] += val, dif[gid[r + 1]] -= val;
} // add_range
void join_chain(int u, int v) {
while (top[u] != top[v]) {
if (dep[top[u]] > dep[top[v]]) swap(u, v);
ids[ids_sz++] = dfn[top[v]];
ids[ids_sz++] = dfn[v] + 1;
v = par[top[v]];
}
if (dep[u] > dep[v]) swap(u, v);
ids[ids_sz++] = dfn[u];
ids[ids_sz++] = dfn[v] + 1;
} // join_chain
void add_chain(int u, int v, int64_t val) {
while (top[u] != top[v]) {
if (dep[top[u]] > dep[top[v]]) swap(u, v);
add_range(dfn[top[v]], dfn[v], val);
v = par[top[v]];
}
if (dep[u] > dep[v]) swap(u, v);
add_range(dfn[u], dfn[v], val);
} // add_chain
__attribute__((__always_inline__)) __inline void join_subtree(int u) {
ids[ids_sz++] = dfn[u]; ids[ids_sz++] = dfn[u] + sz[u];
} // join_subtree
__attribute__((__always_inline__)) __inline void add_subtree(int u, int64_t val) {
add_range(dfn[u], dfn[u] + sz[u] - 1, val);
} // add_subtree
int cmp_int(const void *x, const void *y) { return *(int*)x - *(int*)y; }
__attribute__((__always_inline__)) __inline int bs_getid(int id) {
int low = 0, high = ids_sz, ans = 0;
while (low <= high) {
int mid = (low + high) >> 1;
if (ids[mid] <= id) low = mid + 1, ans = mid;
else high = mid - 1;
}
return ans;
} // bs_getid
void divide(int l, int r, int ql, int qr) {
if (l == r) {
for (int i = ql; i <= qr; ++i) ans[qry[i]] = l;
return ;
}
int mid = (l + r) >> 1;
static int qryl[Maxn], qryr[Maxn];
int sz_qryl = 0, sz_qryr = 0;
ids_sz = 0; ids[ids_sz++] = 0;
for (int i = l; i <= mid; ++i) {
if (b[i].type == CHAIN) join_chain(b[i].u, b[i].v);
else join_subtree(b[i].u);
}
if (ids_sz <= BUC_SORT_SIZE)
qsort(ids, ids_sz, sizeof(int), cmp_int);
else {
static int bucket[Maxn] = { };
for (int i = 0; i < ids_sz; ++i) bucket[ids[i]]++;
ids_sz = 0;
for (int i = 0; i <= n; ++i)
for (; bucket[i]; --bucket[i]) ids[ids_sz++] = i;
}
int new_sz = 1;
for (int i = 1; i < ids_sz; ++i)
if (ids[i] != ids[i - 1]) ids[new_sz++] = ids[i];
ids_sz = new_sz;
for (int i = 0; i < ids_sz; ++i) gid[ids[i]] = i;
for (int i = l; i <= mid; ++i) {
if (b[i].type == CHAIN) add_chain(b[i].u, b[i].v, b[i].val);
else add_subtree(b[i].u, b[i].val);
}
for (int i = 1; i < ids_sz; ++i) dif[i] += dif[i - 1];
for (int i = ql; i <= qr; ++i) {
int id = qry[i];
int64_t sum = dif[bs_getid(dfn[id])];
if (sum >= a[id]) qryl[sz_qryl++] = id;
else qryr[sz_qryr++] = id, a[id] -= sum;
}
memset(dif, 0, ids_sz * sizeof *dif);
int qry_start = ql;
for (int i = 0; i < sz_qryl; ++i) qry[qry_start++] = qryl[i];
for (int i = 0; i < sz_qryr; ++i) qry[qry_start++] = qryr[i];
int div = ql + sz_qryl;
divide(l, mid, ql, div - 1);
divide(mid + 1, r, div, qr);
} // divide
int main(int argc, const char *argv[]) {
fread(_I_Buffer, 1, _I_Buffer_Size, stdin);
clear_graph();
n = read(), m = read();
for (int i = 0; i < n; ++i) a[i] = read();
for (int i = 0; i < n - 1; ++i) {
int u = read() - 1, v = read() - 1;
add_edge(u, v); add_edge(v, u);
}
for (int i = 0; i < m; ++i) {
int op = read(), u = read() - 1, v;
int64_t val;
if (op == 1) {
v = read() - 1; val = read();
b[i] = (struct operation) { .type = CHAIN, .u = u, .v = v, .val = val };
}
else {
val = read();
b[i] = (struct operation) { .type = SUBTREE, .u = u, .v = -1, .val = val };
}
}
dfs1(0, -1, 0);
dfs2(0, 0);
for (int i = 0; i < n; ++i) qry[i] = i;
divide(0, m, 0, n - 1);
for (int i = 0; i < n; ++i) {
if (ans[i] == m) puts("alive");
else printf("%d\n", ans[i] + 1);
}
exit(EXIT_SUCCESS);
} // main
总结 整体二分每一层的复杂度不能和 有关。
例题 3 WD与地图
题目描述
题目背景
WD 整日沉浸在地图中,无法自拔……
题目描述
CX 让 WD 研究的地图可以看做是 个点, 条边的有向图,由于政府正在尝试优化人民生活,他们会废弃一些无用的道路来把省下的钱用于经济建设。
城市都有各自的发达程度 。为了方便管理,政府将整个地图划分为一些地区,两个点 在一个地区当且仅当 可以互相到达。政府希望知道一些时刻某个地区的前 发达城市的发达程度总和,以此推断建设的情况。
也就是说,共有三个操作:
1 a b
表示政府废弃了从 连向 的边,保证这条边存在。
2 a b
表示政府把钱用于建设城市 ,使其发达程度增加 。
3 a b
表示政府希望知道 城市所在地区发达程度前 大城市的发达程度之和。如果地区中的城市不足 个输出该地区所有城市的发达程度总和。输入格式
第一行两个数 ,表示共 个点, 条边, 次询问。
第二行 个正整数,表示 ,即每个城市的发达程度。
接下来 行每行两个数 ,表示初始时有一条从 连向 的边。
接下来 行,表示 组询问,格式如题目描述。
输出格式
对于每个询问操作,输出一个数,表示发达程度之和。
样例 1#Input
5 8 8 4 2 1 1 3 2 5 4 2 5 3 1 3 4 5 5 1 1 5 1 4 3 3 1 1 4 5 3 3 3 3 4 1 3 1 5 3 2 4 1 5 3 2 3 4
样例 1#Output
1 1 4 10 10
数据范围
,删除操作个数;
;
.
保证任何时刻发达程度 ,无重边(反向边不算重边)无自环。
首先套路性地将删边变为加边,于是问题就变成了:动态加边,改变一个点权值大小,和那个奇怪的查询。
先考虑如果是无向图怎么做。无向图很显然加边就是把两个连通块合并,那么可以对于每一个连通块维护一个动态开点线段树。线段树下标即为权值,查询时就在线段树上二分,加边时就把两个连通块所代表的线段树合并一下。于是这样可以做到 。
然后考虑有向图。我们发现无向图的好处就是每加一条边,都可以把两个连通块合并;而有向图上就不行了。所以我们现在要求出:对于每条边,什么时候可以把这条边所连接的两个端点所在的强连通块合并起来。求出了这个之后就可以像无向图一样直接线段树合并了。
那么问题时怎么找出这些时间点呢?这里就可以用到整体二分的技巧。不难发现,每条边的存在是有时间的。先考虑暴力对于每一条边都二分,那么就是二分时间 ,把所有 时间内就存在的边加入到当前的图里,然后跑一边tarjan
,看这条边所连接的两个端点是否在同一个强连通块中。如果在,说明答案在 之前;否则在 之后。然后发现,对于不同的时间 所对应的图是不同的;但对于不同的边且相同的 ,此时的图是相同的。所以我们可以用整体二分的技巧来优化暴力二分。注意在整体二分过程中,我们无法承受每次都加入出现时间在 内的边;于是可以考虑利用上一次递归的结果,使用可撤销并查集来维护当前区间tarjan
缩点的结果,这样总时间复杂度就是 或 。
参考代码
#include <bits/stdc++.h>
using namespace std;
static constexpr int Maxn = 1e5 + 5;
static constexpr int Maxm = 2e5 + 5;
static constexpr int Maxq = 2e5 + 5;
static mt19937 __gen(std::chrono::steady_clock::now().time_since_epoch().count());
#define int int64_t
int n, m, q;
int val[Maxn];
struct Query {
int op, a, b;
Query() { }
~Query() = default;
Query(const Query &__other) = default;
Query(int op, int a, int b) : op(op), a(a), b(b) { }
};
Query qry[Maxq];
struct Edge {
int u, v, t;
Edge() { }
~Edge() = default;
Edge(const Edge &__other) = default;
Edge(int u, int v, int t) : u(u), v(v), t(t) { }
};
vector<Edge> edges, tedge[Maxq];
int fa[Maxn];
int fnd(int u) { return fa[u] == u ? u : fa[u] = fnd(fa[u]); }
void dsu_merge(int u, int v) {
u = fnd(u), v = fnd(v);
if (u != v) fa[u] = v;
} // dsu_merge
struct di_edge { int to, nxt; } de[Maxm << 1];
int head[Maxn], tot;
inline void add_edge(int u, int v) {
de[++tot] = (di_edge){v, head[u]}; head[u] = tot;
} // add_edge
int dfn_time;
int dfn[Maxn], low[Maxn];
stack<int> stk;
bool instk[Maxn];
void tarjan(int u) {
dfn[u] = low[u] = ++dfn_time;
instk[u] = true;
stk.push(u);
for (int ei = head[u]; ei; ei = de[ei].nxt) {
int v = de[ei].to;
if (!dfn[v]) tarjan(v), low[u] = min(low[u], low[v]);
else if (instk[v]) low[u] = min(low[u], dfn[v]);
}
if (dfn[u] == low[u]) {
do {
int v = stk.top(); stk.pop(); instk[v] = false;
dsu_merge(u, v);
if (v == u) break;
} while (true);
}
} // tarjan
void divide(int l, int r, const vector<Edge> &e) {
if (e.empty()) return ;
if (l == r) {
for (const auto &x: e) tedge[l].push_back(x);
return ;
}
if (int(e.size()) == 1) {
int time = q + 1;
const auto &[u, v, t] = e.front();
if (fnd(u) == fnd(v)) time = t;
tedge[time].push_back(e.front());
return ;
}
int mid = (l + r) >> 1;
vector<Edge> e_l, e_r;
vector<pair<int, int>> e_edges;
vector<int> e_nodes;
for (const auto &E: e) {
if (E.t <= mid) {
int u = fnd(E.u), v = fnd(E.v);
e_edges.push_back({u, v});
head[u] = head[v] = 0;
e_nodes.push_back(u);
e_nodes.push_back(v);
}
}
tot = 0;
for (const auto &E: e_edges) {
int u = E.first, v = E.second;
add_edge(u, v);
dfn[u] = low[u] = 0;
dfn[v] = low[v] = 0;
instk[u] = instk[v] = false;
}
dfn_time = 0;
while (!stk.empty()) stk.pop();
for (const int &u: e_nodes) if (!dfn[u]) tarjan(u);
int now_id = 0;
vector<pair<int, int>> cpar;
for (const auto &E: e) {
if (E.t <= mid) {
int u = e_nodes[now_id++];
int v = e_nodes[now_id++];
if (fnd(u) == fnd(v)) e_l.push_back(E);
else e_r.push_back(E);
cpar.push_back({u, fa[u]});
cpar.push_back({v, fa[v]});
} else {
e_r.push_back(E);
}
}
for (const int &u: e_nodes) fa[u] = u;
divide(l, mid, e_l);
for (auto [u, fau]: cpar) fa[u] = fau;
divide(mid + 1, r, e_r);
} // divide
namespace sgt {
static constexpr int Maxs = ::Maxm * 50;
int tot, N, ls[Maxs], rs[Maxs];
int cnt[Maxs];
long long sum[Maxs];
inline int newnode(void) { return ++tot; }
void init(int n) {
tot = 0, N = n;
ls[0] = rs[0] = 0;
cnt[0] = sum[0] = 0;
} // init
void update(int &p, int pos, int val, int l = 1, int r = N) {
if (!p) p = newnode();
cnt[p] += val, sum[p] += pos * val;
if (l == r) return ;
int mid = (l + r) >> 1;
if (pos <= mid) update(ls[p], pos, val, l, mid);
else update(rs[p], pos, val, mid + 1, r);
} // update
long long query(int p, int k, int l = 1, int r = N) {
if (cnt[p] <= k) return sum[p];
if (l == r) return sum[p] / cnt[p] * k;
int mid = (l + r) >> 1;
if (cnt[rs[p]] >= k) return query(rs[p], k, mid + 1, r);
else return sum[rs[p]] + query(ls[p], k - cnt[rs[p]], l, mid);
} // query
int merge(int u, int v) {
if (!u || !v) return u | v;
cnt[u] += cnt[v], sum[u] += sum[v];
ls[u] = merge(ls[u], ls[v]);
rs[u] = merge(rs[u], rs[v]);
return u;
} // merge
} // namespace sgt
int root[Maxn];
int32_t main(void) {
ios_base::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
cout << fixed << setprecision(12);
cin >> n >> m >> q;
for (int i = 1; i <= n; ++i) cin >> val[i];
set<pair<int, int>> _edge;
for (int i = 1; i <= m; ++i) {
int u, v; cin >> u >> v;
_edge.insert({u, v});
}
for (int i = q; i >= 1; --i) {
cin >> qry[i].op >> qry[i].a >> qry[i].b;
if (qry[i].op == 1) edges.push_back(Edge(qry[i].a, qry[i].b, i)), _edge.erase({qry[i].a, qry[i].b});
if (qry[i].op == 2) val[qry[i].a] += qry[i].b;
}
for (auto [u, v]: _edge) edges.push_back(Edge(u, v, 0));
_edge.clear();
assert((int)edges.size() == m);
iota(fa + 1, fa + n + 1, 1);
divide(0, q + 1, edges);
iota(fa + 1, fa + n + 1, 1);
int maxv = *max_element(val + 1, val + n + 1);
sgt::init(maxv);
for (int i = 1; i <= n; ++i) {
root[i] = sgt::newnode();
sgt::update(root[i], val[i], 1);
}
vector<int> ans;
for (int i = 0; i <= q; ++i) {
if (!i || qry[i].op == 1) {
for (auto [u, v, t]: tedge[i]) {
u = fnd(u), v = fnd(v);
if (u == v) continue;
fa[v] = u;
root[u] = sgt::merge(root[u], root[v]);
}
} else if (qry[i].op == 2) {
int u = qry[i].a;
int &w = val[u], rt = fnd(u);
sgt::update(root[rt], w, -1);
w -= qry[i].b;
sgt::update(root[rt], w, 1);
} else {
int u = qry[i].a, k = qry[i].b;
int rt = fnd(u);
int res = sgt::query(root[rt], k);
ans.push_back(res);
}
}
for (; !ans.empty(); ans.pop_back())
cout << ans.back() << endl;
exit(EXIT_SUCCESS);
} // main
总结 从例题2,3可以看出,整体二分可以用来解决一些存在时间类型的问题。
题目描述
You are given an integer array and an integer array .
You have to calculate the array defined as follows: .
Input
The first line of input contains a single integer ().
The second line of input contains integers ().
The third line of input contains integers ().
Output
Output integers .
Example 1#Input
8 1 2 3 4 5 6 7 8 8 7 6 5 4 3 2 1
Example 1#Output
7 5 3 3 1 3 5 7
一眼没有明显的做法,我们尝试着先化简式子。首先可以把绝对值拆掉,拆成两块分别算。
然后每一块的式子是这样的:
我们枚举 ,每个 中询问是 次 ,其中 。我们发现 根本不好用数论方式化简。
于是我们考虑整体二分。用一次整体二分,将 变为 。这样式子就变为了 其中 , 是每一次整体二分的数值。这个式子我们用一下 反演之后就变为了:。我们现在要算的就是 。这玩意儿显然可以记忆化后暴力。于是,整体二分里面的复杂度就是 。
所以,总的时间复杂度是 ,常数较小,可以通过。
参考代码
#include <bits/stdc++.h>
using namespace std;
static constexpr int Maxn = 1e5 + 5;
namespace Solve {
int N, T, mu[Maxn], prime[Maxn], sz;
bool notprime[Maxn];
int ans[Maxn], arr[Maxn], arrk[Maxn];
int lens[Maxn], facs[Maxn][200];
int storage[Maxn];
void init(int n = Maxn - 1) {
memset(storage, -1, sizeof(storage));
memset(lens, 0, sizeof(lens));
for (int i = 1; i <= n; ++i)
for (int j = 1; j * j <= i; ++j) {
if (i % j == 0) {
facs[i][++lens[i]] = j;
if (j * j != i) facs[i][++lens[i]] = i / j;
}
}
memset(notprime, false, sizeof(notprime));
notprime[0] = notprime[1] = true;
memset(mu, 0, sizeof(mu));
mu[1] = 1;
for (int i = 2; i <= n; ++i) {
if (!notprime[i]) prime[++sz] = i, mu[i] = -1;
for (int j = 1; j <= sz && i * prime[j] <= n; ++j) {
notprime[i * prime[j]] = true;
if (i % prime[j] == 0) break;
mu[i * prime[j]] = -mu[i];
}
}
} // Solve::init
void divide(int l, int r, int vl, int vr) {
if (l > r) return ;
if (vl == vr) {
for (int i = l; i <= r; ++i) ans[arrk[i]] = vl;
return ;
}
int vm = (1LL * vl + 1LL * vr + 1) >> 1;
static int arrlk[Maxn], arrrk[Maxn];
int cnt1 = 0, cnt2 = 0, _c = l - 1;
int i, j, T, _, sum, d;
for (i = l; i <= r; ++i) {
for (T = arrk[i], sum = 0, _ = 1; _ <= lens[T]; ++_) {
if (storage[d = facs[T][_]] == -1) {
int &res = storage[d]; res = 0;
for (j = d; j <= N; j += d) res += (vm <= arr[j]);
}
sum += storage[d] * mu[d];
}
((sum == 0) ? arrlk[++cnt1] : arrrk[++cnt2]) = T;
}
for (i = l; i <= r; ++i)
for (T = arrk[i], _ = 1; _ <= lens[T]; ++_)
storage[facs[T][_]] = -1;
for (int i = 1; i <= cnt1; ++i) arrk[++_c] = arrlk[i];
for (int i = 1; i <= cnt2; ++i) arrk[++_c] = arrrk[i];
divide(l, l + cnt1 - 1, vl, vm - 1);
divide(l + cnt1, r, vm, vr);
} // Solve::divide
void Main(int n, int a[], int b[], int c[]) {
for (int k = 1; k <= n; ++k) {
c[k] = -2e9; N = (int)(n / k);
static int vals[Maxn] = { };
int ll = vals[0] = 0;
for (int i = 1; i <= N; ++i)
vals[++ll] = arr[i] = b[i * k], arrk[i] = i;
sort(vals + 1, vals + ll + 1);
ll = unique(vals + 1, vals + ll + 1) - vals - 1;
for (int i = 1; i <= N; ++i)
arr[i] = lower_bound(vals + 1, vals + ll + 1, arr[i]) - vals;
divide(1, N, 1, N);
for (int i = 1; i <= N; ++i)
c[k] = max(c[k], a[i * k] + vals[ans[i]]);
}
} // Solve::main
} // namespace Solve
int main(void) {
int n; cin >> n;
Solve::init(n);
static int a[Maxn] = { }, b[Maxn] = { };
for (int i = 1; i <= n; ++i) cin >> a[i];
for (int i = 1; i <= n; ++i) cin >> b[i];
static int c1[Maxn] = { }, c2[Maxn] = { };
for (int i = 1; i <= n; ++i) a[i] = -a[i];
Solve::Main(n, a, b, c1);
for (int i = 1; i <= n; ++i) a[i] = -a[i];
for (int i = 1; i <= n; ++i) b[i] = -b[i];
Solve::Main(n, a, b, c2);
for (int i = 1; i <= n; ++i) b[i] = -b[i];
for (int i = 1; i <= n; ++i)
printf("%d%c", max(c1[i], c2[i]), " \n"[i == n]);
exit(EXIT_SUCCESS);
} // main
总结 整体二分可以将多次询问无法解决的 或 式变为 ,然后推式子优化解法。
例题 5 [FJOI2015]火星商店问题
题目描述
题目描述
火星上的一条商业街里按照商店的编号 ,依次排列着 个商店。商店里出售的琳琅满目的商品中,每种商品都用一个非负整数 来标价。每个商店每天都有可能进一些新商品,其标价可能与已有商品相同。
火星人在这条商业街购物时,通常会逛这条商业街某一段路上的所有商店,譬如说商店编号在区间 中的商店,从中挑选一件自己最喜欢的商品。每个火星人对商品的喜好标准各不相同。
通常每个火星人都有一个自己的喜好密码 。对每种标价为 的商品,喜好密码为 的火星人对这种商品的喜好程度与 异或 的值成正比。也就是说, 的值越大,他就越喜欢该商品。
每个火星人的购物卡在所有商店中只能购买最近 天内(含当天)进货的商品。另外,每个商店都有一种特殊商品不受进货日期限制,每位火星人在任何时刻都可以选择该特殊商品。每个商店中每种商品都能保证供应,不存在商品缺货的问题。
对于给定的按时间顺序排列的事件,计算每个购物的火星人的在本次购物活动中最喜欢的商品,即输出 的最大值。这里所说的按时间顺序排列的事件是指以下两种事件:
0 s v
,表示编号为 的商店在当日新进一种标价为 的商品。
1 l r x d
,表示一位火星人当日在编号在 的商店购买 天内的商品,该火星人的喜好密码为 。输入格式
第一行两个正整数 ,分别表示商店总数和事件总数。
第二行中有 个整数,第 个整数表示商店 的特殊商品标价。
接下来的 行,每行表示一个事件。每天的事件按照先事件 ,后事件 的顺序排列。
输出格式
对于每个事件 ,输出一行一个整数表示答案。
样例 1#Input
4 6 1 2 3 4 1 1 4 1 0 0 1 4 0 1 3 1 1 1 1 0 1 1 1 1 1 1 1 2 1 2
样例 1#Output
5 0 2 5
数据范围
对于 的数据,所有输入的整数在 范围内。
考虑到询问是给定一定范围内的集合 与一个整数 ,求 。我们可以考虑用trie树做。但是trie树的构建需要把所有元素都放到一个trie树里面再查询,朴素的实现肯定是不行的。
注意到trie树中的查询其实是一个二分的过程:对于一个 ,假设现在从高往低算到了第 位,则这一层二分的值的后 位应该都是 1
,若 的第 位是 且当前范围内存在 ,或者 的第 位是 且当前范围内存在 ,则答案应该加上 ,否则就不加。
若我们对于每一个询问这样暴力二分,时间复杂度就是 。但是注意到,在对于不同的询问,每一个二分里面相同的二分值的判定是相同的 (因为原始的序列并没有发生任何变化)。于是我们可以用整体二分优化二分的过程。接着我们考虑二分中,对于二分值 判定的范围是啥。
显然,我们可以把所有的商品分为两部分: 的,和 的。对于一个询问,它要满足的商品 的条件是:。这显然就变成了一个静态二维数点问题了。所以我们在整体二分中把这一层的所有的询问和商品缓存下来,然后做两次二维数点就可以了。时间复杂度为 。
注意,还需要额外考虑每一个商店的特殊标价。这个怎么做都可以了。时间复杂度 或 。
所以总时间复杂度为 ,空间复杂度为 。
参考代码
#include <bits/stdc++.h>
using namespace std;
static constexpr int Maxn = 1e5 + 5;
static constexpr int LOG = 17;
static constexpr int Maxw = (1 << LOG) - 1;
int n, m, cn, qn;
int ans1[Maxn], ans2[Maxn];
struct Goods {
int w, id, time;
} sw[Maxn], c[Maxn];
struct Query {
int wl, wr, w;
int id, ql, qr;
} q[Maxn];
struct fenwick_tree {
int a[Maxn];
void add(int x, int v) { for (; x < Maxn; x += x & -x) a[x] += v; }
int ask(int x) const { int r = 0; for (; x; x -= x & -x) r += a[x]; return r; }
int qry(int l, int r) const { return ask(r) - ask(l - 1); }
} bit_left, bit_right;
struct Data {
int x, y, w, id;
Data() { memset(this, 0, sizeof(*this)); }
Data(const Data &x) { *this = x; }
Data(int x, int y, int w, int id)
: x(x), y(y), w(w), id(id) { }
friend bool operator < (const Data &x, const Data &y) {
if (x.x != y.x) return x.x < y.x;
return x.id < y.id;
}
};
int cnt_point[Maxn];
void calc(Goods points[], int p_sz, Query qry[], int q_sz) {
memset(cnt_point, 0, (q_sz + 1) << 2);
int sz = 0;
static Data dd[Maxn << 3] = { };
for (int i = 1; i <= p_sz; ++i)
dd[++sz] = Data(points[i].id, points[i].time, 1, -1);
for (int i = 1; i <= q_sz; ++i) {
dd[++sz] = Data(qry[i].qr, qry[i].wr, 1, i);
dd[++sz] = Data(qry[i].qr, qry[i].wl - 1, -1, i);
dd[++sz] = Data(qry[i].ql - 1, qry[i].wr, -1, i);
dd[++sz] = Data(qry[i].ql - 1, qry[i].wl - 1, 1, i);
}
sort(dd + 1, dd + sz + 1);
for (int i = 1; i <= sz; ++i) {
if (dd[i].id == -1) bit_left.add(dd[i].y, dd[i].w);
else cnt_point[dd[i].id] += bit_left.ask(dd[i].y) * dd[i].w;
}
for (int i = 1; i <= sz; ++i)
if (dd[i].id == -1) bit_left.add(dd[i].y, -dd[i].w);
} // calc
Goods w_left[Maxn], w_right[Maxn];
Query q_left[Maxn], q_right[Maxn];
void divide1(int wl, int wr, int l, int r, int ql, int qr, int dep) {
if (l > r || ql > qr) return ;
if (wl == wr) return ;
int wm = (wl + wr) >> 1; // [wl, wm]: "0...", (wm, wr]: "1..."
int w_left_sz, w_right_sz, q_left_sz, q_right_sz;
w_left_sz = w_right_sz = 0, q_left_sz = q_right_sz = 0;
for (int i = l; i <= r; ++i) {
if (sw[i].w <= wm) {
w_left[++w_left_sz] = sw[i];
bit_left.add(sw[i].id, 1);
}
else {
w_right[++w_right_sz] = sw[i];
bit_right.add(sw[i].id, 1);
}
}
for (int i = ql; i <= qr; ++i) {
if (((q[i].w >> dep) & 1) != 0) { // simulate trie tree
if (bit_left.qry(q[i].ql, q[i].qr) != 0) q_left[++q_left_sz] = q[i], ans1[q[i].id] |= (1 << dep);
else q_right[++q_right_sz] = q[i];
}
else {
if (bit_right.qry(q[i].ql, q[i].qr) != 0) q_right[++q_right_sz] = q[i], ans1[q[i].id] |= (1 << dep);
else q_left[++q_left_sz] = q[i];
}
}
for (int i = l; i <= r; ++i) {
if (sw[i].w <= wm) bit_left.add(sw[i].id, -1);
else bit_right.add(sw[i].id, -1);
}
for (int i = 1; i <= w_left_sz; ++i) sw[l + i - 1] = w_left[i];
for (int i = 1; i <= w_right_sz; ++i) sw[l + w_left_sz + i - 1] = w_right[i];
for (int i = 1; i <= q_left_sz; ++i) q[ql + i - 1] = q_left[i];
for (int i = 1; i <= q_right_sz; ++i) q[ql + q_left_sz + i - 1] = q_right[i];
const int w_cnt = w_left_sz, q_cnt = q_left_sz;
divide1(wl, wm, l, l + w_cnt - 1, ql, ql + q_cnt - 1, dep - 1);
divide1(wm + 1, wr, l + w_cnt, r, ql + q_cnt, qr, dep - 1);
} // divide1
void divide2(int wl, int wr, int l, int r, int ql, int qr, int dep) {
if (l > r || ql > qr) return ;
if (wl == wr) return ;
int wm = (wl + wr) >> 1; // [wl, wm]: "0...", (wm, wr]: "1..."
int w_left_sz, w_right_sz, q_left_sz, q_right_sz;
w_left_sz = w_right_sz = 0, q_left_sz = q_right_sz = 0;
int ql_top = ql - 1, qr_top = qr + 1;
for (int i = l; i <= r; ++i) {
if (c[i].w <= wm) w_left[++w_left_sz] = c[i];
else w_right[++w_right_sz] = c[i];
}
for (int i = ql; i <= qr; ++i) {
if (((q[i].w >> dep) & 1) != 0) q_left[++q_left_sz] = q[i];
else q_right[++q_right_sz] = q[i];
}
calc(w_left, w_left_sz, q_left, q_left_sz);
for (int i = 1; i <= q_left_sz; ++i) {
if (cnt_point[i] != 0) q[++ql_top] = q_left[i], ans2[q_left[i].id] |= (1 << dep);
else q[--qr_top] = q_left[i];
}
calc(w_right, w_right_sz, q_right, q_right_sz);
for (int i = 1; i <= q_right_sz; ++i) {
if (cnt_point[i] != 0) q[--qr_top] = q_right[i], ans2[q_right[i].id] |= (1 << dep);
else q[++ql_top] = q_right[i];
}
assert(ql_top + 1 == qr_top);
for (int i = 1; i <= w_left_sz; ++i) c[l + i - 1] = w_left[i];
for (int i = 1; i <= w_right_sz; ++i) c[l + w_left_sz + i - 1] = w_right[i];
const int w_cnt = w_left_sz, q_cnt = ql_top - ql + 1;
divide2(wl, wm, l, l + w_cnt - 1, ql, ql + q_cnt - 1, dep - 1);
divide2(wm + 1, wr, l + w_cnt, r, ql + q_cnt, qr, dep - 1);
} // divide2
int main(void) {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; ++i) {
scanf("%d", &sw[i].w);
sw[i].id = i, sw[i].time = 0;
}
int day = 0;
for (int i = 1; i <= m; ++i) {
int op; scanf("%d", &op);
if (op == 0) {
++day, ++cn;
scanf("%d %d", &c[cn].id, &c[cn].w);
c[cn].time = day;
}
else {
++qn; int d;
scanf("%d %d %d %d", &q[qn].ql, &q[qn].qr, &q[qn].w, &d);
q[qn].wr = day, q[qn].wl = q[qn].wr - d + 1;
q[qn].id = qn;
}
}
divide1(0, Maxw, 1, n, 1, qn, LOG - 1);
divide2(0, Maxw, 1, cn, 1, qn, LOG - 1);
for (int i = 1; i <= qn; ++i)
printf("%d\n", max(ans1[i], ans2[i]));
exit(EXIT_SUCCESS);
} // main
总结 整体二分可用于在数据结构上的二分 ( 例如trie树的二分过程, 线段树二分等 )。
习题 1 [ZJOI2013] K大数查询
习题 2 [CTSC2018] 混合果汁
习题 3 [HNOI2015] 接水果
习题 4 [国家集训队] 矩阵乘法
习题 5 [POI2011]MET-Meteors
习题 6 [HNOI2016]网络
习题 7 Pastoral Oddities
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】