离线分治算法

整体二分

引言

整体二分用于解决一些决策性单调的分治问题,其核心思想就是利用分治将同一个范围内的答案统一处理。

使用条件

  • 单个询问可以用二分解决(可二分性)。
  • 修改对答案贡献互相独立,修改之间互不影响
  • 允许离线。

例题

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
*/

cdq 分治

一般统计点对 \((i, j)\) 的贡献,分治时分为三种统计:

  • \(i, j \in [l, mid]\)
  • \(i, j \in [mid + 1, r]\)
  • \(i \in [l, mid], r \in [mid + 1, r]\)

递归处理一、三类点,单独统计第二类点。

另外 cdq 分治还可以优化 dp。

例题

P3810 【模板】三维偏序(陌上花开)

先在 cdq 外按 \(a\) 排序,每次递归里面再按 \(b\) 排序,那么合并的时候只需要处理 \(c\) 的贡献即可。

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 2e5 + 5;
int n, m, tot, lst, buc[N];
struct Node {
	int a, b, c, w, ans;

	bool operator == (Node &A) const {
		return A.a == a && A.b == b && A.c == c;
	}
} a[N], b[N];

namespace BIT {
	int t[N];

	inline int lowbit(int x) {
		return x & -x;
	}

	inline void add(int x, int k) {
		for( ; x <= m ; x += lowbit(x))
			t[x] += k;

		return ;
	}

	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) {
	if(l == r) return ;

	int mid = (l + r) >> 1, i = l, j = mid + 1;

	solve(l, mid), solve(mid + 1, r);

	sort(a + l, a + mid + 1, [&](Node a, Node b) {
		return a.b < b.b;
	});
	sort(a + mid + 1, a + r + 1, [&](Node a, Node b) {
		return a.b < b.b;
	});

	for( ; j <= r ; ++ j) {
		while(i <= mid && a[i].b <= a[j].b) {
			add(a[i].c, a[i].w);
			++ i;
		}

		a[j].ans += query(a[j].c);
	}

	-- i;
	for( ; i >= l ; -- i)
		add(a[i].c, -a[i].w);

	return ;
}

signed main() {
	ios_base :: sync_with_stdio(NULL);
	cin.tie(nullptr);
	cout.tie(nullptr);

	cin >> n >> m;
	for(int i = 1 ; i <= n ; ++ i)
		cin >> b[i].a >> b[i].b >> b[i].c;

	sort(b + 1, b + 1 + n, [&](Node a, Node b) {
		if(a.a == b.a && a.b == b.b) return a.c < b.c;
        else if(a.a == b.a) return a.b < b.b;
        else return a.a < b.a;
	});

	for(int i = 1 ; i <= n ; ++ i) {
		if(a[lst] == b[i]) ++ a[lst].w;
		else a[++ tot] = b[i], a[tot].w = 1, lst = tot;
	}

	solve(1, tot);

	for(int i = 1 ; i <= n ; ++ i)
		buc[a[i].ans + a[i].w - 1] += a[i].w;

	for(int i = 0 ; i < n ; ++ i)
		cout << buc[i] << '\n';

	return 0;
}

P4093 [HEOI2016/TJOI2016] 序列

\(dp_i\) 表示以 \(i\) 为结尾的答案。

可以写出朴素 dp 的式子:

\[dp_i = \max_{j \in [1, i)} \{ dp_j \} + 1 (a_i \le \min \{ a_j, b_j \} \land \max \{ a_i, b_i \} \le a_j) \]

转移条件是一个三维偏序的形式,可以 cdq 分治优化。

需要注意的是,统计左区间对右区间的贡献时要预先知道左区间的答案,所以要在算贡献之前递归完左区间。

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 1e5 + 5;
int n, m, x, y, a[N], mn[N], mx[N], dp[N], pos[N];

namespace BIT {
	int t[N];

	inline int lowbit(int x) {
		return x & -x;
	}

	inline void add(int x, int k) {
		for( ; x <= 1e5 ; x += lowbit(x))
			t[x] = max(t[x], k);

		return ;
	}

	inline void del(int x) {
		for( ; x <= 1e5 ; x += lowbit(x))
			t[x] = 0;

		return ;
	}

	inline int query(int x) {
		int res = 0;
		
		for( ; x ; x -= lowbit(x))
			res = max(res, t[x]);

		return res;
	}
}

using namespace BIT;

inline void solve(int l, int r) {
	if(l == r) {
		dp[l] = max(dp[l], 1ll);

		return ;
	}

	int mid = (l + r) >> 1, i = l, j = mid + 1;

	solve(l, mid);

	for(int i = l ; i <= r ; ++ i)
		pos[i] = i;

	sort(pos + l, pos + mid + 1, [&](int x, int y) {
		return mx[x] < mx[y];
	});
	sort(pos + mid + 1, pos + r + 1, [&](int x, int y) {
		return a[x] < a[y];
	});

	for( ; j <= r ; ++ j) {
		while(i <= mid && mx[pos[i]] <= a[pos[j]]) {
			add(a[pos[i]], dp[pos[i]]);
			++ i;
		}

		dp[pos[j]] = max(dp[pos[j]], query(mn[pos[j]]) + 1);
	}

	-- i;
	for( ; i >= l ; -- i)
		del(a[pos[i]]);

	solve(mid + 1, r);

	return ;
}

signed main() {
	ios_base :: sync_with_stdio(NULL);
	cin.tie(nullptr);
	cout.tie(nullptr);

	cin >> n >> m;
	for(int i = 1 ; i <= n ; ++ i)
		cin >> a[i], mn[i] = mx[i] = a[i];
	for(int i = 1 ; i <= m ; ++ i) {
		cin >> x >> y;

		mn[x] = min(mn[x], y);
		mx[x] = max(mx[x], y);
	}

	solve(1, n);

	int ans = 0;

	for(int i = 1 ; i <= n ; ++ i)
		ans = max(ans, dp[i]);

	cout << ans;

	return 0;
}
posted @ 2025-06-26 15:30  endswitch  阅读(55)  评论(0)    收藏  举报