算法学习笔记(1):CDQ分治
CDQ分治
对比普通分治
把一种问题划分成不同子问题, 递归处理子问题内部的答案, 再考虑合并子问题的答案。
再看CDQ分治
有广泛的应用, 但我不会。 但在接下来的题目体会到大概: 将可能产生的对于答案的贡献分为两类:
与 内部产生的贡献, 其贡献已在递归内部计算。 与 之间可能产生的贡献, 这就是我们思考的重点。
题目理解
分治
P7883 平面最近点对(加强加强版
题目大意:在同一平面内, 给定一些点,求最近的点对。
题解:
一眼看出
(代码就不必贴了吧)
根据人类启发式智慧, 和对于事物的普遍认知规律, 我们考虑如何对暴力算法进一步降低时间复杂度。不难想到, 若平面内只有两个点, 那最近点对便可以瞬间求出, 那三个呢?四个呢?我们如何利用已知的信息来更快的得到我们的答案?
分治法就来了, 我们将平面一直折半划分, 划分到只有一个或两个点, 可以快速得到答案, 对于更大区间的答案我们就考虑如何利用的这些小区间的答案来优化。 假设:我们已经算出
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
constexpr int N = 4e5 + 10;
inline int read() {
int x = 0, f = 1; char c = getchar();
while (c < '0' || c > '9') {
if (c == '-') f = -1;
c = getchar();
}
while (c >= '0' && c <= '9') {
x = (x << 3) + (x << 1) + (c ^ 48);
c = getchar();
}
return x * f;
}
int n;
struct Node{
int x, y;
}P[N];
bool cmpx(Node a, Node b) {
if (a.x == b.x) return a.y < b.y;
return a.x < b.x;
}
inline bool cmpy(int a, int b) { return P[a].y < P[b].y; }
inline long long min(long long x, long long y) { return x < y ? x : y; }
//inline double fabs(double x) { return x < 0 ? -x : x; }
inline long long dist(int i, int j) { return 1ll * (P[i].x - P[j].x) * (P[i].x - P[j].x) + 1ll * (P[i].y - P[j].y) * (P[i].y - P[j].y); }
int tmp[N];
long long merge(int l, int r) {
int mid = l + r >> 1, cnt = 0;
long long d = 1e18;
if (l == r) return d;
if (l + 1 == r) return dist(l, r);
d = min(merge(l, mid), merge(mid + 1, r));
for (int i = l; i <= r; i++)
if (1ll * (P[mid].x - P[i].x) * (P[mid].x - P[i].x) < d) tmp[++cnt] = i;
sort(tmp + 1, tmp + 1 + cnt, cmpy);
for (int i = 1; i < cnt; i++)
for (int j = i + 1; j <= cnt && 1ll * (P[tmp[j]].y - P[tmp[i]].y) * (P[tmp[j]].y - P[tmp[i]].y) < d; j++)
d = min(d, dist(tmp[i], tmp[j]));
return d;
}
int main() {
n = read();
for (int i = 1; i <= n; i++) P[i].x = read(), P[i].y = read();
sort(P + 1, P + 1 + n, cmpx);
printf("%lld\n", merge(1, n));
return 0;
}
CDQ分治
P3810 【模板】三维偏序(陌上花开)
前置知识——逆序对
如果你会求逆序对的话, 那么对于三维偏序, 我们可以先
流程: 1.
2. 计算
这样排序加上分治时间复杂度带两个log, 但分治的过程和归并排序及其相似, 所以我们不用
附上代码
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
const int MAXN = 2e5 + 10;
int n, k;
struct Ele{
int a, b, c;
}S[N];
struct P{
int b, c, num, ans;
}S1[N];
bool Ecmp(int i, int j) {
return S[i].a == S[j].a && S[i].b == S[j].b && S[i].c == S[j].c;
}
bool cmpa(Ele x, Ele y) {
if (x.a == y.a) {
if (x.b == y.b) return x.c < y.c;
return x.b < y.b;
}
return x.a < y.a;
}
bool cmpb(P x, P y) {
if (x.b == y.b) return x.c < y.c;
return x.b < y.b;
}
int cntS = 0;
struct B_tr{
int tr[MAXN];
int lowbit(int x) { return x & -x; }
void modify(int x, int y) {
while (x <= k) {
tr[x] += y;
x += lowbit(x);
}
return;
}
int query(int x) {
int res = 0;
while (x > 0) {
res += tr[x];
x -= lowbit(x);
}
return res;
}
}t;
void Solve(int l, int r) {
int mid = l + r >> 1;
if (l == r) return;
Solve(l, mid); Solve(mid + 1, r);
int i = l, j = mid + 1;
while (j <= r) {
while (i <= mid && S1[i].b <= S1[j].b) {
t.modify(S1[i].c, S1[i].num);
i++;
}
S1[j].ans += t.query(S1[j].c);
j++;
}
for (j = l; j < i; j++) t.modify(S1[j].c, -S1[j].num);
inplace_merge(S1 + l, S1 + mid + 1, S1 + r + 1, cmpb);
return;
}
int f[N];
int main() {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++) {
int a, b, c; scanf("%d%d%d", &a, &b, &c);
S[i] = (Ele){a, b, c};
}
sort(S + 1, S + 1 + n, cmpa);
int cnt = 0;
for (int i = 1; i <= n; i++) {
cnt++;
if (!Ecmp(i, i + 1) || i + 1 > n) {
S1[++cntS] = (P){S[i].b, S[i].c, cnt, 0};
cnt = 0;
}
}
Solve(1, cntS);
for (int i = 1; i <= cntS; i++) f[S1[i].ans + S1[i].num - 1] += S1[i].num;
for (int i = 0; i < n; i++) printf("%d\n", f[i]);
return 0;
}
P3157 [CQOI2011] 动态逆序对
如果说上一道题你体会不到CDQ分治与普通分治的区别, 那么这道题, CDQ分治的特点会更明显。
题目大意:
现在给出
其实就是加上了时间这一维, 我们考虑删掉一个数时, 会对逆序对数产生多少负贡献, 也就是消失了几对逆序对。 分为两类:
定义删掉的数为
满足 。 满足 。
所以CDQ分治时不仅要考虑左区间对右区间的贡献, 还要考虑右区间对左区间的贡献。
贴代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define int long long
using namespace std;
const int N = 1e5 + 10;
const int M = 5e4 + 10;
int n, m;
struct Ele{
int v, t = 5e4 + 10, cnt;
}S[N];
int pos[N];
bool cmpv(Ele x, Ele y) {
if (x.v == y.v) return x.t < y.t;
return x.v < y.v;
}
bool cmpt(Ele x, Ele y) {
if (x.t == y.t) return x.v < y.v;
return x.t < y.t;
}
struct B_tr {
int tr[N << 1];
void clear() { memset(tr, 0, sizeof(tr)); }
int lowbit(int x) { return x & -x; }
void modify(int x, int y) {
while (x <= N) {
tr[x] += y;
x += lowbit(x);
}
return;
}
int query(int x) {
int res = 0;
while (x > 0) {
res += tr[x];
x -= lowbit(x);
}
return res;
}
}t;
int ans = 0;
void input() {
for (int i = 1; i <= n; i++) {
ans += t.query(n) - t.query(S[i].v);
t.modify(S[i].v, 1);
}
t.clear();
return;
}
void Solve(int l, int r) {
int mid = l + r >> 1;
if (l == r) return;
Solve(l, mid); Solve(mid + 1, r);
sort(S + l, S + mid + 1, cmpv);
sort(S + mid + 1, S + r + 1, cmpv);
int i = r, j = mid;
while (i >= mid + 1) {
while (j >= l && S[i].v < S[j].v) {
t.modify(S[j].t, 1);
j--;
}
S[i].cnt += t.query(N) - t.query(S[i].t);
i--;
}
for (i = mid; i > j; i--) t.modify(S[i].t, -1);
i = l, j = mid + 1;
while (i <= mid) {
while (j <= r && S[i].v > S[j].v) {
t.modify(S[j].t, 1);
j++;
}
S[i].cnt += t.query(N) - t.query(S[i].t);
i++;
}
for (i = mid + 1; i < j; i++) t.modify(S[i].t, -1);
return;
}
int sum[M];
signed main() {
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%lld", &S[i].v);
pos[S[i].v] = i;
}
input();
for (int i = 1; i <= m; i++) {
int x; scanf("%lld", &x);
S[pos[x]].t = i;
}
Solve(1, n);
sort(S + 1, S + 1 + n, cmpt);
for (int i = 1; i <= m; i++) sum[i] = sum[i - 1] + S[i].cnt;
for (int i = 1; i <= m; i++) printf("%lld\n", ans - sum[i - 1]);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App