整体二分和 CDQ 分治

整体二分

多个二分具有相似判断条件可以一起做的性质,可以一起判断。可能不算太鲜明的一个算法。

P3527

一次模拟下雨是把所有国家的一起下了,不妨所有国家一起二分。二分定义域为时间轴,初始把所有国家都挂在 \(k / 2\) 上,然后根据结果分别缩小范围,但每一层虽然是若干个区间但是还是那样扫过去。用线段树维护,可以做到 \(O(n \log^2 n)\)。(\(n,m,k\) 同阶)
https://www.luogu.com.cn/blog/78372/parallel-binsearch
这个里面告诉我,还可以 \(O(n \log n)\)。【待补】

CDQ 分治

cdq 分治类似点分治,可以使用一个基准,处理一些区间的组合。

有一个数轴,数轴上有一些二元组(或者区间)。cdq 分治是将所有区间归到一个 \([l, r, mid]\) 等价类中,意思是,区间位于 \((l, r)\) 中,并且穿过 \(mid\)。容易发现每一个区间被归到恰好一个等价类。

这样分,可以对很多二元组信息的维护形式产生优势,就像点分治可以让一条链差分成两个和根的路径一样。

比如,你可以对左右做差分,从一个固定点扫一遍所有点就得到差分信息(双指针,排序这类都属于这个),达到降维目的
你可以考虑其是一个修改-询问组,或者其他一些右边基于左边的组,并且批量处理,变成静态问题
可以安排分治/递归顺序,使得在 cdq 分治到某一个区间的时候,前面的所有区间都遍历完了。或者类似一个线段树分治的结构。

对于二元组,它起到的作用,和对于树上路径,点分治起到的作用类似。

P3810

\(k\) 维偏序用 bitset 直接暴力怎么做:
\(A_{i,j}\) 维护是否 \(a_i \le a_j\)。这个排序之后扫一遍过去就可以维护了。
最朴素的做法,开 \(k\) 个数组分别维护,最后合并即可。

\(3\) 维偏序,\(1e5\)。怎么办,首先由于数组最后也要合并,只需要用一个数组一直存即可。然后发现空间 \(1e10 = 1\operatorname{GB}\) 过不去,考虑分块每次只算一部分内容,假设块长为 \(l\),那么空间复杂度 \(O(\cfrac{nl}{\omega})\),时间复杂度 \(O(\cfrac{n^3}{l\omega})\)。显然空间不超限制的时候尽量增大 \(l\),取 \(l = 30000\) 可以过。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define f(i, a, b) for(int i = (a); i <= (b); i++)
#define cl(i, n) i.clear(),i.resize(n);
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int inf = 1e9;
void cmax(int &x, int y) {if(x < y) x = y;}
void cmin(int &x, int y) {if(x > y) x = y;}
struct tin {
    int a, b, c, ind;
}q[100010];
bitset<100010> x[30010];
bool cmpa(tin a, tin  b) {return a.a < b.a;}
bool cmpb(tin a, tin  b) {return a.b < b.b;}
bool cmpc(tin a, tin  b) {return a.c < b.c;}
int ans[200010];
const int m = 30000;
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
    //time_t start = clock();
    //think twice,code once.
    //think once,debug forever.
    int n, k; cin >> n >> k;
    f(i, 1, n) {
        cin >> q[i].a >> q[i].b >> q[i].c;
        q[i].ind = i;
    }
    int num = (n + m - 1) / m;
    f(i, 1, num ){
        int kl = (m * (i - 1) + 1), kr = min(n, m * i);
        bitset<100010> std;
        vector<int> v;
        sort(q + 1, q + n + 1, cmpa); 
        f(i, 1, n) {
            std[q[i].ind] = 1;
            v.push_back(q[i].ind);
            if(i == n || q[i].a != q[i+1].a) {
                for(int j : v) {
                    if(j >= kl && j <= kr) x[j - kl + 1] = std;
                    
                }
                v.clear();
            }
        }
        f(i, 1, n) std[i] = 0;
        sort(q + 1, q + n + 1, cmpb); 
        f(i, 1, n) {
            std[q[i].ind] = 1;
            v.push_back(q[i].ind);
            if(i == n || q[i].b != q[i+1].b) {
                for(int j : v) {
                    if(j >= kl && j <= kr) x[j - kl + 1] &= std;
                }
                v.clear();
            }
        }
        f(i, 1, n) std[i] = 0;
        sort(q + 1, q + n + 1, cmpc); 
    
        f(i, 1, n ) {
            std[q[i].ind] = 1;
            v.push_back(q[i].ind);
            if(i == n || q[i].c != q[i+1].c) {
                for(int j : v) {
                    if(j >= kl && j <= kr) x[j - kl + 1] &= std;
                }
                v.clear();
            }
        }    
        f(i, 1, kr - kl + 1) {
            ans[x[i].count() - 1] ++;
        }
    }
    f(i, 0, n - 1) cout << ans[i] << endl;
    
    //time_t finish = clock();
    //cout << "time used:" << (finish-start) * 1.0 / CLOCKS_PER_SEC <<"s"<< endl;
    return 0;
}

ARC159F

【题意】
给定一个长度为 \(2n\) 的数组,求有几种划分方式使得数组划分为若干个长度为偶数的区间(连续子数组),并且这些区间都没有绝对众数。

【分析】

绝对众数的深刻性质使得我们有一个 cdq 分治找到并处理所有有绝对众数 \(v\) 的区间的方式。在这里怎么用呢?

考虑容斥,所有合法划分方式等于所有划分方式减去所有不合法的划分方式。后一部分我们考虑在第一个不合法的时候去除它(dp)。

考虑 dp,\(dp_i\) 表示 \(i\) 这里画一条线,这之前都合法的划分方式数。然后有

\[dp_i = \sum \limits_{j < i} dp_j - \sum \limits_{j < i, [j, i] has~prior} dp_j \]

我们 cdq 分治可以处理左边这个,因为分治到叶子的时候前面的 dp 值全部计算好了。然后考虑后面的位置,其实是一个 \(j \rightarrow i\) 的二元组,然后怎么求这些二元组就跟绝对众数那个方式一样。于是求出来的时候减掉即可。

#include<bits/stdc++.h>
using namespace std;
#define int long long
//use ll instead of int.
#define f(i, a, b) for(int i = (a); i <= (b); i++)
#define cl(i, n) i.clear(),i.resize(n);
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 1e9;
//#define cerr if(false)cerr
//#define freopen if(false)freopen
#define watch(x) cerr  << (#x) << ' '<<'i'<<'s'<<' ' << x << endl
void pofe(int number, int bitnum) {
    string s; f(i, 0, bitnum) {s += char(number & 1) + '0'; number >>= 1; } 
    reverse(s.begin(), s.end()); cerr << s << endl; 
    return;
}
template <typename TYP> void cmax(TYP &x, TYP y) {if(x < y) x = y;} 
template <typename TYP> void cmin(TYP &x, TYP y) {if(x > y) x = y;}
//调不出来给我对拍!
//use std::array.
int a[2 * 500200]; int dp[500200]; const int mod = 998244353; int sum; 
int cnt[2 * 500200]; int s[500200];
void cdq(int l, int r) {
    if(l == r) {sum += dp[l] += sum; sum %= mod; dp[l] %= mod; return; } 
    int mid = (l + r) >> 1;
    cdq(l, mid); vector<int> may; int mx = 0;
    f(i, mid, r - 1) {
        cnt[a[2 * i + 1]] ++; cnt[a[2 * i + 2]] ++; 
        if(cnt[a[2 * i + 1]] > cnt[mx]) mx = a[2 * i + 1];
        if(cnt[a[2 * i + 2]] > cnt[mx]) mx = a[2 * i + 2];
        if(cnt[mx] > i - mid + 1) may.push_back(mx);
    }
    f(i, 2 * mid + 1, 2 * r) cnt[a[i]]--; 
    for(int i = mid - 1; i >= l; i --) {
        cnt[a[2 * i + 1]] ++; cnt[a[2 * i + 2]] ++; 
        if(cnt[a[2 * i + 1]] > cnt[mx]) mx = a[2 * i + 1];
        if(cnt[a[2 * i + 2]] > cnt[mx]) mx = a[2 * i + 2];
        if(cnt[mx] > mid - i) may.push_back(mx);
    }
    f(i, 2 * l + 1, 2 * mid) cnt[a[i]]--; 
    sort(may.begin(), may.end());
    int len = unique(may.begin(), may.end()) - may.begin() - 1;
    f(it, 0, len) {
        
        int cur = may[it];
        s[l] = 0;
        f(j, l, r - 1) {
            s[j + 1] = s[j] + (a[j * 2 + 1] == cur ? 1 : -1) + (a[j * 2 + 2] == cur ? 1 : -1);
        }
        vector<int> lord, rord; 
        f(i, l, mid) lord.push_back(i); 
        f(i, mid + 1, r) rord.push_back(i);
        auto cmp = [=](int x, int y) {
            return s[x] < s[y];
        };
        sort(lord.begin(), lord.end(), cmp); sort(rord.begin(), rord.end(), cmp);
        auto rpt = rord.begin(), lpt = lord.begin(); int memo = 0;
        while(rpt != rord.end()) {
            while(lpt != lord.end() && s[*lpt] < s[*rpt]) memo += dp[*lpt], lpt++, memo %= mod;
            dp[*rpt] -= memo; dp[*rpt] += mod; dp[*rpt] %= mod; rpt++; 
        }
        while(rpt != rord.end()) {dp[*rpt] -= memo; dp[*rpt] += mod; dp[*rpt] %= mod; rpt++; }
    }
    
    cdq(mid + 1, r);
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
    //freopen();
    //freopen();
    //time_t start = clock();
    //think twice,code once.
    //think once,debug forever.
    int n; cin >> n; f(i, 1, 2 * n) cin >> a[i];
    dp[0] = 1; cdq(0, n); 
    cout << dp[n] << endl;
    //time_t finish = clock();
    //cout << "time used:" << (finish-start) * 1.0 / CLOCKS_PER_SEC <<"s"<< endl;
    return 0;
}
/*
2023/4/11
start thinking at 9:40


start coding at 16:03
finish debugging at 18:09
*/

5e5, 600ms,快的。

posted @ 2022-12-11 22:01  OIer某罗  阅读(21)  评论(0编辑  收藏  举报