整体二分和 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\) 这里画一条线,这之前都合法的划分方式数。然后有
我们 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,快的。