整体二分
引言
整体二分用于解决一些决策性单调的分治问题,其核心思想就是利用分治将同一个范围内的答案统一处理。
使用条件
- 单个询问可以用二分解决(可二分性)。
- 修改对答案贡献互相独立,修改之间互不影响。
- 允许离线。
例题
P3527 [POI 2011] MET-Meteors
单组询问可二分,操作相互独立,考虑整体二分。
考虑二分判定问题。有解的可能性随时间推移而增大,则时间作为一个单调域可以在其上整体二分。
代码:
#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int N = 3e5 + 5;
int n, m, k, o[N], p[N], l[N], r[N], a[N], P[N], ans[N];
vector<int> start, w[N], rem[N];
namespace BIT {
int t[N];
inline int lowbit(int x) {
return x & -x;
}
inline void upd(int x, int k) {
for( ; x <= m ; x += lowbit(x))
t[x] += k;
return ;
}
inline void add(int l, int r, int k) {
if(l <= r) {
upd(l, k);
upd(r + 1, -k);
}
else {
upd(1, k), upd(r + 1, -k);
upd(l, k), upd(m + 1, -k);
}
}
inline int query(int x) {
int res = 0;
for( ; x ; x -= lowbit(x))
res += t[x];
return res;
}
}
using namespace BIT;
inline void solve(int L, int R, vector<int> q) {
// cerr << q.size() << '\n';
if(q.empty()) return ;
if(L == R) {
// cerr << "!";
for(auto i : q) ans[i] = L;
return ;
}
vector<int> lq, rq;
int mid = (L + R) >> 1;
for(int i = L ; i <= mid ; ++ i)
add(l[i], r[i], a[i]);
// cerr << "!";
for(auto i : q) {
// cerr << "!";
__int128 sum = 0;
for(auto j : w[i])
sum += query(j);
if(p[i] <= sum) lq.pb(i);
else {
rq.pb(i);
p[i] -= sum;
}
// cerr << "sum:" << (long long)sum << '\n';
}
for(int i = L ; i <= mid ; ++ i)
add(l[i], r[i], -a[i]);
solve(L, mid, lq);
solve(mid + 1, R, rq);
return ;
}
signed main() {
ios_base :: sync_with_stdio(NULL);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n >> m;
for(int i = 1 ; i <= m ; ++ i)
cin >> o[i], w[o[i]].pb(i);
for(int i = 1 ; i <= n ; ++ i)
cin >> p[i], P[i] = p[i];
cin >> k;
for(int i = 1 ; i <= k ; ++ i)
cin >> l[i] >> r[i] >> a[i];
for(int i = 1 ; i <= n ; ++ i)
start.pb(i);
solve(1, k, start);
for(int i = 1 ; i <= n ; ++ i)
rem[ans[i]].pb(i);
memset(t, 0, sizeof t);
for(int i = 1 ; i <= k ; ++ i) {
add(l[i], r[i], a[i]);
for(auto j : rem[i]) {
__int128 sum = 0;
for(auto h : w[j])
sum += query(h);
if(sum < P[j]) ans[j] = -1;
// cerr << j << ' ' << P[j] << ' ' << (long long)sum << ' ' << ans[j] << '\n';
}
}
for(int i = 1 ; i <= n ; ++ i)
if(ans[i] == -1) cout << "NIE\n";
else cout << ans[i] << '\n';
return 0;
}
P2617 Dynamic Rankings
简要题意:单点修区间 \(k\) th。
首先考虑弱化版:不带修。则单次询问可以二分,且询问相互独立,可以整体二分。
则带修版本可以看作是在其上增加了插入和删除操作,并到操作序列里即可。
代码:
#include <bits/stdc++.h>
// #define int long long
#define pb push_back
using namespace std;
const int N = 3e5 + 5;
const int INF = 1e9;
int n, Q, tot, a[N], l[N], r[N], k[N], x[N], y[N], ans[N], flag[N];
char op[N];
vector<int> start;
namespace BIT {
int t[N];
inline int lowbit(int x) {
return x & -x;
}
inline void add(int x, int k) {
for( ; x <= n ; x += lowbit(x))
t[x] += k;
return ;
}
inline int ask(int x) {
int res = 0;
for( ; x ; x -= lowbit(x))
res += t[x];
return res;
}
inline int query(int l, int r) {
return ask(r) - ask(l - 1);
}
}
using namespace BIT;
inline void solve(int L, int R, vector<int> q) {
if(q.empty()) return ;
// cerr << L << ' ' << R << '\n';
// cerr << "!";
if(L == R) {
// cerr << "!";
for(auto i : q) ans[i] = L;
return ;
}
int mid = (L + R) >> 1;
vector<int> ql, qr;
for(auto i : q) {
// cerr << "!";
if(op[i] == 'C') {
if(y[i] <= mid) {
ql.pb(i);
add(x[i], flag[i]);
}
else qr.pb(i);
}
else {
int v = query(l[i], r[i]);
if(v >= k[i]) ql.pb(i);
else {
k[i] -= v;
qr.pb(i);
}
}
}
// cerr << "ql.size:" << ql.size() << '\n';
// cerr << "qr.size:" << qr.size() << '\n';
for(auto i : q)
if(op[i] == 'C' && y[i] <= mid) add(x[i], -flag[i]);
// for(auto i : ql) {
// cerr << op[i] << ' ';
// if(op[i] == 'C') {
// cerr << x[i] << ' ' << y[i] << ' ' << flag[i] << '\n';
// }
// else {
// cerr << l[i] << ' ' << r[i] << ' ' << k[i] << '\n';
// }
// }
solve(L, mid, ql);
solve(mid + 1, R, qr);
return ;
}
signed main() {
// freopen("P2617_2.in", "r", stdin);
// freopen("ans.out", "w", stdout);
ios_base :: sync_with_stdio(NULL);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n >> Q;
for(int i = 1 ; i <= n ; ++ i) {
++ tot;
cin >> a[i];
op[tot] = 'C';
x[tot] = i, y[tot] = a[i], flag[tot] = 1;
}
for(int i = 1 ; i <= Q ; ++ i) {
cin >> op[++ tot];
if(op[tot] == 'C') {
cin >> x[tot] >> y[tot];
flag[tot] = 1;
op[++ tot] = 'C';
x[tot] = x[tot - 1], y[tot] = a[x[tot - 1]], flag[tot] = -1;
a[x[tot]] = y[tot - 1];
swap(x[tot], x[tot - 1]), swap(y[tot], y[tot - 1]), swap(flag[tot], flag[tot - 1]);
}
else cin >> l[tot] >> r[tot] >> k[tot];
}
for(int i = 1 ; i <= tot ; ++ i)
start.pb(i);
solve(0, 1e9, start);
for(int i = 1 ; i <= tot ; ++ i)
if(op[i] == 'Q') cout << ans[i] << '\n';
return 0;
}
P1527 [国家集训队] 矩阵乘法
静态矩阵第 \(k\) 小,将整体二分中的数据结构改用二维树状数组维护即可。
#include <bits/stdc++.h>
// #define int long long
#define pb emplace_back
using namespace std;
const int N = 505;
const int M = 6e4 + 5;
int n, Q, tot, x[N * N], y[N * N], k[N * N + M], xx[N * N + M], yy[N * N + M], xxx[N * N + M], yyy[N * N + M], op[N * N + M], ans[N * N + M];
vector<int> start;
namespace BIT {
int t[N][N];
inline int lowbit(int x) {
return x & -x;
}
inline void add(int x, int y, int k) {
for( ; x <= n ; x += lowbit(x))
for(int z = y ; z <= n ; z += lowbit(z))
t[x][z] += k;
return ;
}
inline int ask(int x, int y) {
int res = 0;
for( ; x ; x -= lowbit(x))
for(int z = y ; z ; z -= lowbit(z))
res += t[x][z];
return res;
}
inline int query(int x, int y, int xx, int yy) {
return ask(xx, yy) - ask(x - 1, yy) - ask(xx, y - 1) + ask(x - 1, y - 1);
}
}
using namespace BIT;
inline void solve(int L, int R, vector<int> q) {
if(q.empty()) return ;
if(L == R) {
for(auto i : q) ans[i] = L;
return ;
}
int mid = (L + R) >> 1;
vector<int> ql, qr;
for(auto i : q) {
if(op[i] == 1) {
if(k[i] <= mid) {
ql.pb(i);
add(x[i], y[i], 1);
}
else qr.pb(i);
}
else {
int v = query(xx[i], yy[i], xxx[i], yyy[i]);
if(v >= k[i]) ql.pb(i);
else {
// cerr << i << ' ' << k[i] << ' ' << v << '\n';
qr.pb(i);
k[i] -= v;
}
}
}
for(auto i : q)
if(op[i] == 1 && k[i] <= mid) add(x[i], y[i], -1);
// cerr << "pos:" << L << ' ' << R << '\n';
// cerr << "ql.size:" << ql.size() << '\n';
// cerr << "qr.size:" << qr.size() << '\n';
// for(auto i : qr) cerr << i << ' ';
// cerr << '\n';
solve(L, mid, ql), solve(mid + 1, R, qr);
return ;
}
signed main() {
ios_base :: sync_with_stdio(NULL);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n >> Q;
for(int i = 1 ; i <= n ; ++ i)
for(int j = 1 ; j <= n ; ++ j) {
op[++ tot] = 1;
x[tot] = i, y[tot] = j;
cin >> k[tot];
}
for(int i = 1 ; i <= Q ; ++ i)
op[++ tot] = 2, cin >> xx[tot] >> yy[tot] >> xxx[tot] >> yyy[tot] >> k[tot];
for(int i = 1 ; i <= tot ; ++ i)
start.pb(i);
solve(1, 1e9, start);
for(int i = 1 ; i <= tot ; ++ i)
if(op[i] == 2) cout << ans[i] << '\n';
// add(1, 1, 1), add(1, 2, 1), add(2, 1, 1), add(2, 2, 1);
// cerr << query(1, 1, 2, 2) << '\n';
// cerr << ask(2, 2) << '\n';
return 0;
}
/*
2 1
2 1
3 4
1 1 2 2 3
*/