cdq分治

cdq分治

主要思想为分治,分为三个部分:

  • 左区间内部。
  • 左区间对右区间。
  • 右区间内部。

一个保险的标准顺序是先处理左区间,再处理左区间对右区间的贡献,最后处理右区间,这样就可以保证时序性了。

注意这种写法在处理左区间对右区间贡献是要先按标号排序分出正确的左右区间,如果是先递归左右区间则不用。

一般cdq问题在分治时需要保证当前左区间和右区间某一维有序,若处理时没有 \(O(\log n)\) 修改、查询的数据结构,可以采用归并排序的方式将时间复杂度从 \(O(n \log^2 n)\) 优化到 \(O(n \log n)\)

处理点对相关问题

给定一个长度为 \(n\) 的序列,统计有一些特性的点对 \((i, j)\) 的数量或找到一对点 \((i, j)\) 使得一些函数的值最大。

基本流程如下:

  • 找到序列中点 \(mid\)
  • 将所有点对分为三类:
    • \(i, j \in [l, mid]\)
    • \(i, j \in [mid + 1, r]\)
    • \(i \in [l, mid], j \in [mid + 1, r]\)
  • 将前两类分治处理,设法处理最后一类,一般为统计左区间对右区间的贡献。

三维偏序

\(n\) 个元素,每个元素有 \(a_i, b_i, c_i\) 三个属性,设 \(f(i)\) 表示满足 \(a_j \leq a_i \and b_j \leq b_i \and c_j \leq c_i \leq i \not = j\)\(j\) 的数量。对于所有 \(d \in [0, n)\) ,求 \(f(i) = d\)\(i\) 的数量。

\(n \leq 10^5\)\(a_i, b_i, c_i \leq 2 \times 10^5\)

先将序列按第一维排序,这样第一维偏序就解决了。

考虑计算 \([l, mid]\)\([mid + 1, r]\) 的贡献,此时只需要满足第二、三维的偏序关系,用树状数组或再用一次 cdq 分治即可。

cdq分治套树状数组:

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7, V = 2e5 + 7;

struct Node {
	int a, b, c, cnt, ans;
} p[N], nd[N];

int ans[N];

int n, vlim, m;

template <class T = int>
inline T read() {
	char c = getchar();
	bool sign = (c == '-');
	
	while (c < '0' || c > '9')
		c = getchar(), sign |= (c == '-');
	
	T x = 0;
	
	while ('0' <= c && c <= '9')
		x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	
	return sign ? (~x + 1) : x;
}

namespace BIT {
int c[V];

inline void update(int x, int k) {
	for (; x <= vlim; x += x & -x)
		c[x] += k;
}

inline int query(int x) {
	int res = 0;
	
	for (; x; x -= x & -x)
		res += c[x];
	
	return res;
}
} // namespace BIT

void cdq(int l, int r) {
	if (l == r)
		return;
	
	int mid = (l + r) >> 1;
	cdq(l, mid), cdq(mid + 1, r);
	sort(nd + l, nd + mid + 1, [](const Node &a, const Node &b) { return a.b < b.b; });
	sort(nd + mid + 1, nd + r + 1, [](const Node &a, const Node &b) { return a.b < b.b; });
	int j = l;
	
	for (int i = mid + 1; i <= r; ++i) {
		for (; j <= mid && nd[j].b <= nd[i].b; ++j)
			BIT::update(nd[j].c, nd[j].cnt);
			
		nd[i].ans += BIT::query(nd[i].c);
	}
	
	for (--j; j >= l; --j)
		BIT::update(nd[j].c, -nd[j].cnt);
}

signed main() {
	n = read(), vlim = read();
	
	for (int i = 1; i <= n; ++i)
		p[i].a = read(), p[i].b = read(), p[i].c = read();
	
	sort(p + 1, p + 1 + n, [](const Node &a, const Node &b) {
        return a.a == b.a ? (a.b == b.b ? a.c < b.c : a.b < b.b) : a.a < b.a;
    });
	
	for (int i = 1, cnt = 1; i <= n; ++i, ++cnt)
		if (p[i].a != p[i + 1].a || p[i].b != p[i + 1].b || p[i].c != p[i + 1].c)
			nd[++m] = p[i], nd[m].cnt = cnt, cnt = 0;
	
	cdq(1, m);
	
	for (int i = 1; i <= m; ++i)
		ans[nd[i].ans + nd[i].cnt - 1] += nd[i].cnt;
	
	for (int i = 0; i < n; ++i)
		printf("%d\n", ans[i]);
	
	return 0;
}

cdq分治套cdq分治:

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7, V = 2e5 + 7;

struct Node {
	int a, b, c, *ans, flag;
	
	inline bool operator == (const Node &rhs) const {
		return a == rhs.a && b == rhs.b && c == rhs.c;
	}
	
	inline bool operator < (const Node &rhs) const {
		return a == rhs.a ? (b == rhs.b ? c < rhs.c : b < rhs.b) : a < rhs.a;
	}
} a[N], b[N], c[N];

int ans[N], f[N];

int n, vlim;

template <class T = int>
inline T read() {
	char c = getchar();
	bool sign = (c == '-');
	
	while (c < '0' || c > '9')
		c = getchar(), sign |= (c == '-');
	
	T x = 0;
	
	while ('0' <= c && c <= '9')
		x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	
	return sign ? (~x + 1) : x;
}

void cdq2(int l, int r) {
	if (l == r)
		return;
	
	int mid = (l + r) >> 1;
	cdq2(l, mid), cdq2(mid + 1, r);
	
	for (int i = l, j = l, k = mid + 1, res = 0; i <= r; ++i)
		if ((k > r || b[j].c <= b[k].c) && j <= mid)
			c[i] = b[j++], res += c[i].flag;
		else {
			c[i] = b[k++];
			
			if (!c[i].flag)
				*c[i].ans += res;
		}
	
	for (int i = l; i <= r; ++i)
		b[i] = c[i];
}

void cdq1(int l, int r) {
	if (l == r)
		return;
	
	int mid = (l + r) >> 1;
	cdq1(l, mid), cdq1(mid + 1, r);
	
	for (int i = l, j = l, k = mid + 1; i <= r; ++i)
		if ((k > r || a[j].b <= a[k].b) && j <= mid)
			b[i] = a[j++], b[i].flag = 1;
		else
			b[i] = a[k++], b[i].flag = 0;
	
	for (int i = l; i <= r; ++i)
		a[i] = b[i];
	
	cdq2(l, r);
}

signed main() {
	n = read(), vlim = read();
	
	for (int i = 1; i <= n; ++i)
		a[i].a = read(), a[i].b = read(), a[i].c = read(), a[i].ans = ans + i;
	
	sort(a + 1, a + 1 + n);
	
	for (int i = n - 1; i; --i)
		if (a[i] == a[i + 1])
			*a[i].ans = *a[i + 1].ans + 1;
	
	cdq1(1, n);
	
	for (int i = 1; i <= n; ++i)
		++f[ans[i]];
	
	for (int i = 0; i < n; ++i)
		printf("%d\n", f[i]);
	
	return 0;
}

P3157 [CQOI2011] 动态逆序对

给出一个 \(1 \sim n\) 的排列,按给出顺序依次删除 \(m\) 个元素,求每次删除一个元素之前整个序列的逆序对数。

\(n \leq 10^5, m \leq 5 \times 10^4\)

逆序对本质就是二维偏序,这里再引入一个时间维即可转化为三维偏序。注意处理逆序对的时候前后都要统计贡献。

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 2e5 + 7;

struct Node {
    int x, k, t;
} nd[N];

ll ans[N];
int pos[N];

int n, m;

template <class T = int>
inline T read() {
	char c = getchar();
	bool sign = (c == '-');
	
	while (c < '0' || c > '9')
		c = getchar(), sign |= (c == '-');
	
	T x = 0;
	
	while ('0' <= c && c <= '9')
		x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	
	return sign ? (~x + 1) : x;
}

namespace BIT {
int c[N];

inline void update(int x, int k) {
	for (; x <= n; x += x & -x)
		c[x] += k;
}

inline int query(int x) {
	int res = 0;
	
	for (; x; x -= x & -x)
		res += c[x];
	
	return res;
}
} // namespace BIT

void cdq(int l, int r) {
	if (l == r)
		return;

	int mid = (l + r) >> 1;
	cdq(l, mid), cdq(mid + 1, r);
	sort(nd + l, nd + mid + 1, [](const Node &a, const Node &b) { return a.x < b.x; });
	sort(nd + mid + 1, nd + r + 1, [](const Node &a, const Node &b) { return a.x < b.x; });
	int j = l;

	for (int i = mid + 1; i <= r; ++i) {
		for (; j <= mid && nd[j].x < nd[i].x; ++j)
			BIT::update(nd[j].k, 1);

		ans[nd[i].t] += BIT::query(n) - BIT::query(nd[i].k);
	}

	for (--j; j >= l; --j)
		BIT::update(nd[j].k, -1);

	sort(nd + l, nd + mid + 1, [](const Node &a, const Node &b) { return a.x > b.x; });
	sort(nd + mid + 1, nd + r + 1, [](const Node &a, const Node &b) { return a.x > b.x; });
	j = l;

	for (int i = mid + 1; i <= r; ++i) {
		for (; j <= mid && nd[j].x > nd[i].x; ++j)
			BIT::update(nd[j].k, 1);

		ans[nd[i].t] += BIT::query(nd[i].k - 1);
	}

	for (--j; j >= l; --j)
		BIT::update(nd[j].k, -1);
}

signed main() {
	n = read(), m = read();

    for (int i = 1; i <= n; ++i) {
    	int x = read();
    	nd[x] = (Node) {i, x, m + 1};
    }

    for (int i = 1; i <= m; ++i)
    	nd[read()].t = i;

    sort(nd + 1, nd + 1 + n, [](const Node &a, const Node &b) { return a.t > b.t; });

    cdq(1, n);

    for (int i = m; i; --i)
    	ans[i] += ans[i + 1];

    for (int i = 1; i <= m; ++i)
        printf("%lld\n", ans[i]);

    return 0;
}

四维偏序

主流做法是cdq分治套cdq分治套树状数组,时间复杂度 \(O(n \log^3 n)\) ,实现细节较多。

高维偏序

对于一般 \(k\) 维偏序问题,利用cdq嵌套求解的复杂度是 \(O(n \log ^{k - 1} n)\) 的。

对于高维偏序,我们一般使用 bitset 解决。

对于每一维(记当前处理维度为 \(i\) ),对于除自己以外的所有元素,把满足第 \(i\) 维偏序的元素编号组成一个集合,把所有集合求交即可。

这个做法是在线的,时间复杂度 \(O(\dfrac{n^2}{\omega})\)

CF1045G AI robots

给出 \(n\) 个三元组 \((x_i, r_i, q_i)\) 以及常数 \(k\) ,两个三元组之间能贡献当且仅当 \(|x_i - x_j| \leq \min(r_i, r_j)\)\(|q_i - q_j| \leq k\) ,求能够贡献的对数。

\(n \leq 10^5, k \leq 20\)

\(r_i\) 降序排序,这样 \(\min(r_i, r_j)\) 就取的是右边的 \(r\) ,然后cdq分治+双指针+树状数组即可。

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1e5 + 7;

struct Node {
	int x, len, q, l, r;
} nd[N];

ll ans;
int n, k;

template <class T = int>
inline T read() {
	char c = getchar();
	bool sign = (c == '-');
	
	while (c < '0' || c > '9')
		c = getchar(), sign |= (c == '-');
	
	T x = 0;
	
	while ('0' <= c && c <= '9')
		x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	
	return sign ? (~x + 1) : x;
}

namespace BIT {
int c[N];

inline void update(int x, int k) {
	for (; x <= n; x += x & -x)
		c[x] += k;
}

inline int query(int x) {
	int res = 0;
	
	for (; x; x -= x & -x)
		res += c[x];
	
	return res;
}
} // namespace BIT

void cdq(int l, int r) {
	if (l == r)
		return;

	int mid = (l + r) >> 1;
	cdq(l, mid), cdq(mid + 1, r);
	sort(nd + l, nd + mid + 1, [](const Node &a, const Node &b) { return a.q < b.q; });
	sort(nd + mid + 1, nd + r + 1, [](const Node &a, const Node &b) { return a.q < b.q; });
	int curl = l, curr = l - 1;

	for (int i = mid + 1; i <= r; ++i) {
		while (curl <= mid && nd[i].q - nd[curl].q > k)
			BIT::update(nd[curl++].x, -1);

		while (curr < mid && nd[curr + 1].q - nd[i].q <= k)
			BIT::update(nd[++curr].x, 1);

		ans += BIT::query(nd[i].r) - BIT::query(nd[i].l - 1);
	}

	for (int i = curl; i <= curr; ++i)
		BIT::update(nd[i].x, -1);
}

signed main() {
	n = read(), k = read();
	vector<int> vec;

	for (int i = 1; i <= n; ++i) {
		nd[i].x = read(), nd[i].len = read(), nd[i].q = read();
		vec.emplace_back(nd[i].x);
	}

	sort(vec.begin(), vec.end());
	vec.erase(unique(vec.begin(), vec.end()), vec.end());

	for (int i = 1; i <= n; ++i) {
		nd[i].l = lower_bound(vec.begin(), vec.end(), nd[i].x - nd[i].len) - vec.begin() + 1;
		nd[i].r = upper_bound(vec.begin(), vec.end(), nd[i].x + nd[i].len) - vec.begin();
		nd[i].x = lower_bound(vec.begin(), vec.end(), nd[i].x) - vec.begin() + 1;
	}

	sort(nd + 1, nd + 1 + n, [](const Node &a, const Node &b) { return a.len > b.len; });
	cdq(1, n);
	printf("%lld", ans);
	return 0;
}

CF848C Goodbye Souvenir

给出一个数组,定义一个数在区间内的权值为最后一次出现的位置减去第一次出现的位置。不重复贡献。\(m\) 次操作,每次单点修改或查询某个区间内的权值和。

\(n, m \leq 10^5\)

\(las_i\) 为上一个同色的位置,考虑讲每个位置的权值设为 \(i - las_i\) ,把区间内的权值求和,就能得到最后一次出现的位置减去第一次出现的前驱。

接下来考虑减去多算的”第一次出现的位置-第一次出现的前驱“,即 \(\sum_{i = l}^r [las_i < l] i\) ,这是个二维数点问题,用cdq分治解决。

因为带修改,所以拿 set 维护前驱即可。

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1e5 + 7;

struct Node {
	int a, b, c, x, op;
} nd[N * 7];

set<int> st[N];

ll ans[N];
int a[N], las[N];

int n, m, cntu, cntq;

template <class T = int>
inline T read() {
	char c = getchar();
	bool sign = (c == '-');
	
	while (c < '0' || c > '9')
		c = getchar(), sign |= (c == '-');
	
	T x = 0;
	
	while ('0' <= c && c <= '9')
		x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	
	return sign ? (~x + 1) : x;
}

namespace BIT {
ll c[N];

inline void clear(int n) {
	memset(c + 1, 0, sizeof(ll) * n);
}

inline void update(int x, int k) {
	for (; x <= n; x += x & -x)
		c[x] += k;
}

inline ll query(int x) {
	ll res = 0;
	
	for (; x; x -= x & -x)
		res += c[x];
	
	return res;
}
} // namespace BIT

inline void update(int x, int i) {
	int res = *prev(st[a[x]].lower_bound(x));
	nd[++cntu] = (Node) {x, las[x], i, x - las[x], 1};
	BIT::update(x, las[x] - res), las[x] = res;
	nd[++cntu] = (Node) {x, las[x], i, res - x, 1};
}

void cdq(int l, int r) {
	if (l == r)
		return;

	int mid = (l + r) >> 1;
	cdq(l, mid), cdq(mid + 1, r);
	sort(nd + l, nd + mid + 1, [](const Node &a, const Node &b) { return a.b < b.b; });
	sort(nd + mid + 1, nd + r + 1, [](const Node &a, const Node &b) { return a.b < b.b; });
	int j = l;

	for (int i = mid + 1; i <= r; ++i) {
		for (; j <= mid && nd[j].b <= nd[i].b; ++j)
			if (nd[j].op == 1)
				BIT::update(nd[j].a, nd[j].x);

		if (!nd[i].op) {
			if (nd[i].x > 0)
				ans[nd[i].x] += BIT::query(nd[i].a);
			else
				ans[-nd[i].x] -= BIT::query(nd[i].a);
		}
	}

	for (--j; j >= l; --j)
		if (nd[j].op == 1)
			BIT::update(nd[j].a, -nd[j].x);
}

signed main() {
	n = read(), m = read();

	for (int i = 1; i <= n; ++i)
		st[i].insert(0);

	for (int i = 1; i <= n; ++i) {
		las[i] = *st[a[i] = read()].rbegin();
		BIT::update(i, i - las[i]);
		nd[++cntu] = (Node) {i, las[i], 0, las[i] - i, 1};
		st[a[i]].insert(i);
	}

	for (int i = 1; i <= m; ++i) {
		if (read() == 1) {
			int x = read();
			st[a[x]].erase(x);
			auto it = st[a[x]].upper_bound(x);

			if (it != st[a[x]].end())
				update(*it, i);

			it = st[a[x] = read()].insert(x).first;
			update(x, i), ++it;

			if (it != st[a[x]].end())
				update(*it, i);
		} else {
			int l = read(), r = read();
			ans[++cntq] = BIT::query(r) - BIT::query(l - 1);
			nd[++cntu] = (Node) {l - 1, l - 1, i, -cntq, 0};
			nd[++cntu] = (Node) {r, l - 1, i, cntq, 0};
		}
	}

	BIT::clear(n);
	cdq(1, cntu);

	for (int i = 1; i <= cntq; ++i)
		printf("%lld\n", ans[i]);

	return 0;
}

优化1D/1D动态规划

以二维最长上升子序列为例,可以列出转移方程:

\[f_i = 1 + \max_{j = 1}^{i - 1} f_j [a_j < a_i] [b_j < b_i] \]

直接转移是 \(O(n^2)\) 的,考虑cdq分治优化,假设当前处理的区间为 \(l, r\) ,流程大致如下:

  • \(l = r\) ,说明 \(f_l\) 已求得,直接返回即可。
  • 递归处理 \([l, mid]\)
  • 处理所有 \(j \in [l, mid], i \in [mid + 1, r]\) 的转移关系。
  • 递归处理 \([mid + 1, r]\)

注意这里必须按标准顺序处理。

P2487 [SDOI2011] 拦截导弹

\(n\) 个导弹,每个导弹有两个参数 \(h, v\) 。求一个最长的序列 \(a\) ,满足 \(h, v\) 不升,输出其长度。

由于可能有多种方案,需要求出对于每个导弹,其成为最长序列中的一项的概率。

\(n \leq 5 \times 10^4\)

第一问和二维LIS是类似的,第二问实际就是包含该导弹的方案数除以总方案数。一个显然的事实是包含该导弹的方案数为前后最长序列的方案数的乘积,于是跑正反两遍cdq优化DP即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 5e4 + 7;

struct Node {
	int h, v, id;
	pair<int, double> f;
};

int n;

template <class T = int>
inline T read() {
	char c = getchar();
	bool sign = (c == '-');
	
	while (c < '0' || c > '9')
		c = getchar(), sign |= (c == '-');
	
	T x = 0;
	
	while ('0' <= c && c <= '9')
		x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	
	return sign ? (~x + 1) : x;
}

namespace Pre {
Node nd[N];

namespace BIT {
pair<int, double> f[N];

inline void update(int x, pair<int, double> k) {
	for (; x; x -= x & -x)
		if (k.first > f[x].first)
			f[x] = k;
		else if (k.first == f[x].first)
			f[x].second += k.second;
}

inline void remove(int x) {
	for (; x; x -= x & -x)
		f[x] = make_pair(0, 0);
}

inline pair<int, double> query(int x) {
	pair<int, double> res = make_pair(0, 0);

	for (; x <= n; x += x & -x)
		if (f[x].first > res.first)
			res = f[x];
		else if (f[x].first == res.first)
			res.second += f[x].second;

	return res;
}
} // namespace BIT

void cdq(int l, int r) {
	if (l == r) {
		++nd[l].f.first;
		return;
	}

	int mid = (l + r) >> 1;
	sort(nd + l, nd + r + 1, [](const Node &a, const Node &b) { return a.id < b.id; });
	cdq(l, mid);
	sort(nd + l, nd + mid + 1, [](const Node &a, const Node &b) { return a.h > b.h; });
	sort(nd + mid + 1, nd + r + 1, [](const Node &a, const Node &b) { return a.h > b.h; });
	int j = l;

	for (int i = mid + 1; i <= r; ++i) {
		for (; j <= mid && nd[j].h >= nd[i].h; ++j)
			BIT::update(nd[j].v, nd[j].f);

		pair<int, double> res = BIT::query(nd[i].v);

		if (res.first > nd[i].f.first)
			nd[i].f = res;
		else if (res.first == nd[i].f.first)
			nd[i].f.second += res.second;
	}

	for (--j; j >= l; --j)
		BIT::remove(nd[j].v);

	cdq(mid + 1, r);
}
} // namespace Pre

namespace Suf {
Node nd[N];

namespace BIT {
pair<int, double> f[N];

inline void update(int x, pair<int, double> k) {
	for (; x <= n; x += x & -x)
		if (k.first > f[x].first)
			f[x] = k;
		else if (k.first == f[x].first)
			f[x].second += k.second;
}

inline void remove(int x) {
	for (; x <= n; x += x & -x)
		f[x] = make_pair(0, 0);
}

inline pair<int, double> query(int x) {
	pair<int, double> res = make_pair(0, 0);

	for (; x; x -= x & -x)
		if (f[x].first > res.first)
			res = f[x];
		else if (f[x].first == res.first)
			res.second += f[x].second;

	return res;
}
} // namespace BIT

void cdq(int l, int r) {
	if (l == r) {
		++nd[l].f.first;
		return;
	}

	int mid = (l + r) >> 1;
	sort(nd + l, nd + r + 1, [](const Node &a, const Node &b) { return a.id < b.id; });
	cdq(mid + 1, r);
	sort(nd + l, nd + mid + 1, [](const Node &a, const Node &b) { return a.h < b.h; });
	sort(nd + mid + 1, nd + r + 1, [](const Node &a, const Node &b) { return a.h < b.h; });
	int j = mid + 1;

	for (int i = l; i <= mid; ++i) {
		for (; j <= r && nd[j].h <= nd[i].h; ++j)
			BIT::update(nd[j].v, nd[j].f);

		pair<int, double> res = BIT::query(nd[i].v);

		if (res.first > nd[i].f.first)
			nd[i].f = res;
		else if (res.first == nd[i].f.first)
			nd[i].f.second += res.second;
	}

	for (--j; j >= mid + 1; --j)
		BIT::remove(nd[j].v);

	cdq(l, mid);
}
} // namespace Suf

signed main() {
	n = read();
	vector<int> vec;

	for (int i = 1; i <= n; ++i) {
		Pre::nd[i].h = Suf::nd[i].h = read();
		vec.emplace_back(Pre::nd[i].v = Suf::nd[i].v = read());
		Pre::nd[i].id = Suf::nd[i].id = i;
	}

	sort(vec.begin(), vec.end());
	vec.erase(unique(vec.begin(), vec.end()), vec.end());

	for (int i = 1; i <= n; ++i) {
		Pre::nd[i].v = lower_bound(vec.begin(), vec.end(), Pre::nd[i].v) - vec.begin() + 1;
		Pre::nd[i].f = make_pair(0, 1);
		Suf::nd[i].v = lower_bound(vec.begin(), vec.end(), Suf::nd[i].v) - vec.begin() + 1;
		Suf::nd[i].f = make_pair(0, 1);
	}

	Pre::cdq(1, n), Suf::cdq(1, n);
	sort(Pre::nd + 1, Pre::nd + 1 + n, [](const Node &a, const Node &b) { return a.id < b.id; });
	sort(Suf::nd + 1, Suf::nd + 1 + n, [](const Node &a, const Node &b) { return a.id < b.id; });
	pair<int, double> ans = make_pair(0, 1);

	for (int i = 1; i <= n; ++i)
		if (Pre::nd[i].f.first > ans.first)
			ans = Pre::nd[i].f;
		else if (Pre::nd[i].f.first == ans.first)
			ans.second += Pre::nd[i].f.second;

	printf("%d\n", ans.first);

	for (int i = 1; i <= n; ++i)
		if (Pre::nd[i].f.first + Suf::nd[i].f.first - 1 == ans.first)
			printf("%.5lf ", Pre::nd[i].f.second * Suf::nd[i].f.second / ans.second);
		else
			printf("0.00000 ");

	return 0;
}

P4027 [NOI2007] 货币兑换

一共 \(n\) 天,每天可以卖出或者买入两种股票,两种股票第 \(i\) 天的价值分别为 \(a_i\)\(b_i\)

每天可以花所有的现金买入股票,这些股票中 \(a\) 股与 \(b\) 股的数量比为 \(r_i\) 。每天也可以把所有的股票以当天的价值卖出,获得现金。求 \(n\) 天后能够获得的最大价值。

必然存在一种最优的买卖方案满足:每次买进操作使用完所有的人民币,每次卖出操作卖出所有的金券。

\(n \leq 10^5\)

设第 \(i\) 天最大收益为 \(f_i\) ,并设 \(f_i\) 可以换为两种股票数量为 \(x_i, y_i\) ,则:

\[x_i = f_i \dfrac{r_i}{a_i r_i + b_i} \\ y_i = f_i \dfrac{1}{a_i r_i + b_i} \]

枚举上一次买入的时间 \(j\) ,可以得到转移方程:

\[f_i = \max \{ f_{i - 1}, \max \{ x_j a_i + y_j b_i \} \} \]

前一项是好处理的,考虑后一项,设转移到 \(f_i\)\(j\) 优于 \(k\) ,则:

\[x_j a_i + y_j b_i > x_k a_i + y_k b_i \\ \dfrac{y_j - y_k}{x_j - x_k} > -\dfrac{a_i}{b_i} \]

很明显 \(x, y\) 都是没有单调性的,无法直接用单调栈或队列建凸壳。考虑使用cdq分治优化,分治时对前一块的 \(x\) 排序,就可以建出凸壳优化转移。

时间复杂度 \(O(n \log n)\)

#include <bits/stdc++.h>
using namespace std;
const double inf = 1e9;
const int N = 1e5 + 7;

struct Node {
	double x, y, k;
	int id;
} nd[N], tmp[N];

double a[N], b[N], rate[N], f[N];
int sta[N];

double S;
int n;

inline double slope(int a, int b) {
	return nd[a].x == nd[b].x ? inf : (nd[b].y - nd[a].y) / (nd[b].x - nd[a].x);
}

void cdq(int l, int r) {
	if (l == r) {
		f[l] = max(f[l], f[l - 1]);
		nd[l].x = f[l] * rate[l] / (a[l] * rate[l] + b[l]);
		nd[l].y = f[l] / (a[l] * rate[l] + b[l]);
		return;
	}

	int mid = (l + r) >> 1, lp = l, rp = mid + 1;

	for (int i = l; i <= r; ++i)
		if (nd[i].id <= mid)
			tmp[lp++] = nd[i];
		else
			tmp[rp++] = nd[i];

	memcpy(nd + l, tmp + l, sizeof(Node) * (r - l + 1));
	cdq(l, mid);
	int top = 0;

	for (int i = l; i <= mid; ++i) {
		while (top > 1 && slope(sta[top], i) > slope(sta[top - 1], sta[top]))
			--top;

		sta[++top] = i;
	}

	int cur = 1;

	for (int i = mid + 1; i <= r; ++i) {
		while (cur < top && slope(sta[cur], sta[cur + 1]) > nd[i].k)
			++cur;

		f[nd[i].id] = max(f[nd[i].id], nd[sta[cur]].x * a[nd[i].id] + nd[sta[cur]].y * b[nd[i].id]);
	}

	cdq(mid + 1, r);
	lp = l, rp = mid + 1, cur = l;

	while (lp <= mid && rp <= r)
		if (nd[lp].x < nd[rp].x)
			tmp[cur++] = nd[lp++];
		else
			tmp[cur++] = nd[rp++];

	while (lp <= mid)
		tmp[cur++] = nd[lp++];

	while (rp <= r)
		tmp[cur++] = nd[rp++];

	memcpy(nd + l, tmp + l, sizeof(Node) * (r - l + 1));
}

signed main() {
	scanf("%d%lf", &n, &S);

	for (int i = 1; i <= n; ++i) {
		scanf("%lf%lf%lf\n", a + i, b + i, rate + i);
		nd[i].x = S * rate[i] / (a[i] * rate[i] + b[i]);
		nd[i].y = S / (a[i] * rate[i] + b[i]);
		nd[i].k = -a[i] / b[i];
		nd[i].id = i, f[i] = S;
	}

	sort(nd + 1, nd + n + 1, [](const Node &a, const Node &b) { return a.k > b.k; });
	cdq(1, n);
	printf("%.3lf", f[n]);
	return 0;
}

P4849 寻找宝藏

在一个四维坐标系中,给定 \(n\) 个点,问有多少种选择点的方案,使得这些点排序后任意坐标单调不降,并且选择的点权和最大,同时输出最大值。

\(n \leq 8 \times 10^4\)

DP方程是容易的,细节在于cdq的嵌套。

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int Mod = 998244353;
const int N = 8e4 + 7;

struct Node {
	pair<ll, int> f;

	int a, b, c, d, flag;
	ll k;

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

int n, vmax, m;

template <class T = int>
inline T read() {
	char c = getchar();
	bool sign = (c == '-');
	
	while (c < '0' || c > '9')
		c = getchar(), sign |= (c == '-');
	
	T x = 0;
	
	while ('0' <= c && c <= '9')
		x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	
	return sign ? (~x + 1) : x;
}

inline int add(int x, int y) {
	x += y;
	
	if (x >= Mod)
		x -= Mod;
	
	return x;
}

namespace BIT {
pair<ll, int> c[N];

inline void update(int x, pair<ll, int> k) {
	for (; x <= n; x += x & -x)
		if (k.first > c[x].first)
			c[x] = k;
		else if (k.first == c[x].first)
			c[x].second = add(c[x].second, k.second);
}

inline pair<ll, int> query(int x) {
	pair<ll, int> res = make_pair(0, 0);

	for (; x; x -= x & -x)
		if (c[x].first > res.first)
			res = c[x];
		else if (c[x].first == res.first)
			res.second = add(res.second, c[x].second);

	return res;
}

inline void remove(int x) {
	for (; x <= n; x += x & -x)
		c[x] = make_pair(0, 0);
}
} // namespace BIT

void cdq2(int l, int r) {
	if (l == r)
		return;

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

	stable_sort(nd + l, nd + mid + 1, [](const Node &a, const Node &b) {
		return a.c == b.c ? a.d < b.d : a.c < b.c;
	});
	stable_sort(nd + mid + 1, nd + r + 1, [](const Node &a, const Node &b) {
		return a.c == b.c ? a.d < b.d : a.c < b.c;
	});

	int j = l;

	for (int i = mid + 1; i <= r; ++i) {
		for (; j <= mid && nd[j].c <= nd[i].c; ++j)
			if (nd[j].flag == 1)
				BIT::update(nd[j].d, nd[j].f);

		if (nd[i].flag == 2) {
			pair<ll, int> res = BIT::query(nd[i].d);

			if (res.first > nd[i].f.first)
				nd[i].f = res;
			else if (res.first == nd[i].f.first)
				nd[i].f.second = add(nd[i].f.second, res.second);
		}
	}

	for (--j; j >= l; --j)	
		if (nd[j].flag == 1)
			BIT::remove(nd[j].d);

	stable_sort(nd + l, nd + r + 1, [](const Node &a, const Node &b) {
		return a.b == b.b ? (a.c == b.c ? a.d < b.d : a.c < b.c) : a.b < b.b;
	});

	cdq2(mid + 1, r);
}

void cdq1(int l, int r) {
	if (l == r) {
		nd[l].f.first += nd[l].k;
		return;
	}

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

	for (int i = l; i <= mid; ++i)
		nd[i].flag = 1;

	for (int i = mid + 1; i <= r; ++i)
		nd[i].flag = 2;

	stable_sort(nd + l, nd + r + 1, [](const Node &a, const Node &b) {
		return a.b == b.b ? (a.c == b.c ? a.d < b.d : a.c < b.c) : a.b < b.b;
	});

	cdq2(l, r);

	stable_sort(nd + l, nd + r + 1, [](const Node &a, const Node &b) {
		return a.a == b.a ? (a.b == b.b ? (a.c == b.c ? a.d < b.d : a.c < b.c) : a.b < b.b) : a.a < b.a;
	});

	cdq1(mid + 1, r);
}

signed main() {
	n = read(), vmax = read();

	for (int i = 1; i <= n; ++i)
		a[i].a = read(), a[i].b = read(), a[i].c = read(), a[i].d = read(), a[i].k = read();
	
	stable_sort(a + 1, a + 1 + n, [](const Node &a, const Node &b) {
		return a.a == b.a ? (a.b == b.b ? (a.c == b.c ? a.d < b.d : a.c < b.c) : a.b < b.b) : a.a < b.a;
	});

	nd[m = 1] = a[1];

	for (int i = 2; i <= n; ++i)
		if (a[i] == nd[m])
			nd[m].k += a[i].k;
		else
			nd[++m] = a[i];

	vector<int> vec;

	for (int i = 1; i <= m; ++i)
		vec.emplace_back(nd[i].d);

	sort(vec.begin(), vec.end());
	vec.erase(unique(vec.begin(), vec.end()), vec.end());

	for (int i = 1; i <= m; ++i)
		nd[i].d = lower_bound(vec.begin(), vec.end(), nd[i].d) - vec.begin() + 1;

	for (int i = 1; i <= m; ++i)
		nd[i].f = make_pair(0, 1);

	cdq1(1, m);
	pair<ll, int> ans = make_pair(0, 0);

	for (int i = 1; i <= m; ++i)
		if (nd[i].f.first > ans.first)
			ans = nd[i].f;
		else if (nd[i].f.first == ans.first)
			ans.second = add(ans.second, nd[i].f.second);

	printf("%lld\n%d", ans.first, ans.second);
	return 0;
}

将动态问题转化为静态问题

一类问题形如:

  • 带修改与查询。
  • 可以离线。
  • 每一个修改均与之后的询问操作相关。

那么可以将所有操作会按照时间cdq分治。假设现在处理的时间区间为 \([l, r]\) ,先递归处理 \([l, mid]\)\([mid + 1, r]\) 的修改-询问关系,再处理修改 \(\in [l, mid]\) 、询问 \(\in [mid + 1, r]\) 的修改-询问关系,统计这部分修改对询问的贡献。

如果修改之间相互独立,则三部分顺序无所谓,否则必须按标准顺序处理。

P4390 [BalkanOI2007] Mokia 摩基亚

给出一个 \(W \times W\) 的网格,\(n\) 次操作,每次操作为下面两种操作中的一种:

  • 给某个格子加上 \(x\)
  • 询问一个矩形中的所有数的和。

\(n \leq 10^5, W \leq 10^6\)

比较板,按上述思路分治即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 7;

struct Node {
	int x, y, k, t, id;
} nd[N];

int ans[N];

int W, n, cntu, cntq;

template <class T = int>
inline T read() {
	char c = getchar();
	bool sign = c == '-';
	
	while (c < '0' || c > '9')
		c = getchar(), sign |= c == '-';
	
	T x = 0;
	
	while ('0' <= c && c <= '9')
		x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	
	return sign ? (~x + 1) : x;
}

namespace BIT {
int c[N];

inline void update(int x, int k) {
	for (; x <= W; x += x & -x)
		c[x] += k;
}

inline int query(int x) {
	int res = 0;
	
	for (; x; x -= x & -x)
		res += c[x];
	
	return res;
}
} // namespace BIT

void cdq(int l, int r) {
	if (l == r)
		return;
	
	int mid = (l + r) >> 1;
	cdq(l, mid), cdq(mid + 1, r);
	sort(nd + l, nd + mid + 1, [](const Node &a, const Node &b) { return a.x < b.x; });
	sort(nd + mid + 1, nd + r + 1, [](const Node &a, const Node &b) { return a.x < b.x; });
	int j = l;

	for (int i = mid + 1; i <= r; ++i) {
		for (; j <= mid && nd[j].x <= nd[i].x; ++j)
			if (!nd[j].id)
				BIT::update(nd[j].y, nd[j].k);

		if (nd[i].id > 0)
			ans[nd[i].id] += BIT::query(nd[i].y);
		else if (nd[i].id < 0)
			ans[-nd[i].id] -= BIT::query(nd[i].y);
	}

	for (--j; j >= l; --j)
		if (!nd[j].id)
			BIT::update(nd[j].y, -nd[j].k);
}

signed main() {
	int op = read();
	W = read() + 1;
	
	while ((op = read()) != 3) {
		if (op == 1) {
			int x = read() + 1, y = read() + 1, k = read();
			++cntu, nd[++n] = (Node) {x, y, k, cntu, 0};
		} else {
			int x = read() + 1, y = read() + 1, xx = read() + 1, yy = read() + 1;
			++cntq;
			nd[++n] = (Node) {xx, yy, 0, cntu, cntq};
			nd[++n] = (Node) {x - 1, yy, 0, cntu, -cntq};
			nd[++n] = (Node) {xx, y - 1, 0, cntu, -cntq};
			nd[++n] = (Node) {x - 1, y - 1, 0, cntu, cntq};
		}
	}
    
	cdq(1, n);
	
	for (int i = 1; i <= cntq; ++i)
		printf("%d\n", ans[i]);
	
	return 0;
}

P4169 [Violet] 天使玩偶/SJY摆棋子

在平面直角坐标系上维护 \(n\) 次操作,每次操作为下面两种操作中的一种:

  • 加入一个点 \((x, y)\)
  • 询问与 \((x, y)\) 曼哈顿距离最小的点。

\(n \leq 3 \times 10^5\)

分情况把绝对值拆开就行了,如对于一个 \(j \leq i, x_j \leq x_i, y_j \leq y_i\) 的情况,曼哈顿距离即为 \((x_i + y_i) - (x_j + y_j)\) ,不难发现限制条件为三维偏序,于是cdq分治处理即可。剩下情况也是类似的。

#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
const int N = 3e5 + 7, V = 2e6 + 7;

struct Node {
	int x, y, id;
} nd[4][N << 1];

int ans[N];

int n, m, tot, cntq;

template <class T = int>
inline T read() {
	char c = getchar();
	bool sign = (c == '-');
	
	while (c < '0' || c > '9')
		c = getchar(), sign |= (c == '-');
	
	T x = 0;
	
	while ('0' <= c && c <= '9')
		x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	
	return sign ? (~x + 1) : x;
}

namespace BIT {
int c[V];

inline void prework() {
	fill(c, c + V, -inf);
}

inline void update(int x, int k) {
	for (; x < V; x += x & -x)
		c[x] = max(c[x], k);
}

inline void remove(int x) {
	for (; x < V; x += x & -x)
		c[x] = -inf;
}

inline int query(int x) {
	int res = -inf;
	
	for (; x; x -= x & -x)
		res = max(res, c[x]);
	
	return res;
}
} // namespace BIT

void solve(int l, int r, Node *nd) {
	if (l == r)
		return;
	
	int mid = (l + r) >> 1;
	solve(l, mid, nd), solve(mid + 1, r, nd);
	sort(nd + l, nd + mid + 1, [](const Node &a, const Node &b) { return a.x < b.x; });
	sort(nd + mid + 1, nd + r + 1, [](const Node &a, const Node &b) { return a.x < b.x; });
	int j = l;

	for (int i = mid + 1; i <= r; ++i) {
		for (; j <= mid && nd[j].x <= nd[i].x; ++j)
			if (!nd[j].id)
				BIT::update(nd[j].y, nd[j].x + nd[j].y);

		if (nd[i].id)
			ans[nd[i].id] = min(ans[nd[i].id], nd[i].x + nd[i].y - BIT::query(nd[i].y));
	}
	
	for (--j; j >= l; --j)
		BIT::remove(nd[j].y);
}

signed main() {
	n = read(), m = read();
	
	for (int i = 1; i <= n; ++i) {
		int x = read(), y = read() + 1;
		nd[0][i] = (Node) {x, y, 0};
		nd[1][i] = (Node) {V - x, y, 0};
		nd[2][i] = (Node) {x, V - y, 0};
		nd[3][i] = (Node) {V - x, V - y, 0};
	}
	
	for (int i = n + 1; i <= n + m; ++i) {
		int op = read(), x = read(), y = read() + 1;
		
		if (op == 1) {
			nd[0][i] = (Node) {x, y, 0};
			nd[1][i] = (Node) {V - x, y, 0};
			nd[2][i] = (Node) {x, V - y, 0};
			nd[3][i] = (Node) {V - x, V - y, 0};
		} else {
			++cntq;
			nd[0][i] = (Node) {x, y, cntq};
			nd[1][i] = (Node) {V - x, y, cntq};
			nd[2][i] = (Node) {x, V - y, cntq};
			nd[3][i] = (Node) {V - x, V - y, cntq};
		}
	}
	
	memset(ans + 1, inf, sizeof(int) * cntq);
	BIT::prework();
	
	for (int i = 0; i < 4; ++i)
		solve(1, n + m, nd[i]);
	
	for (int i = 1; i <= cntq; ++i)
		printf("%d\n", ans[i]);
	
	return 0;
}

P4690 [Ynoi2016] 镜中的昆虫

给定 \(a_{1 \sim n}\)\(m\) 次操作:

  • \(a_{l \sim r}\) 赋值为 \(x\)
  • 查询 \(a_{l \sim r}\) 数字种数。

\(n, m \leq 10^5\)

维护一下每个位置左侧第一个同色点的位置记为 \(pre_i\) ,此时区间数颜色就被转化为了一个经典的二维数点问题,加上时间维即为三维偏序问题。

通过将连续的一段颜色看成一个点的方式,可以证明 \(pre\) 的变化量是 \(O(n + m)\) 的。

证明:设一个值域相同的极长连续段为一个「节点」。注意到如果一个节点整体赋上一个值,那么只有节点中第一个数的 \(pre\) 会被修改。考虑 ODT 的过程,每次修改都是先将两端的节点分裂,然后将中间的节点删掉,再换成同一个节点。分裂和更换的过程,就是删除若干节点,再添加至多三个节点。对 \(pre\) 数组的修改次数和删除的节点个数同阶。而每个节点至多删除一次,因此得证。

那么可以用cdq分治来解决动态的单点加矩形求和问题, \(pre\) 用ODT维护即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7, M = 2e6 + 7;

struct Node {
	int x, y, t, k, id;
} nd[M];

map<int, int> mp;

int a[N], pre[N << 1], lst[N << 1], ans[N];

int n, m, cnt, tot, cntq;

template <class T = int>
inline T read() {
	char c = getchar();
	bool sign = (c == '-');
	
	while (c < '0' || c > '9')
		c = getchar(), sign |= (c == '-');
	
	T x = 0;
	
	while ('0' <= c && c <= '9')
		x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	
	return sign ? (~x + 1) : x;
}

namespace ODT {
struct ODTNode {
	int l, r, k;

	inline bool operator < (const ODTNode &rhs) const {
		return l < rhs.l;
	}
};

set<ODTNode> col[N << 1], all;

inline auto insert(int l, int r, int k) {
	col[k].insert((ODTNode) {l, r, k});
	return all.insert((ODTNode) {l, r, k}).first;
}

inline void remove(int l, int r, int k) {
	col[k].erase((ODTNode) {l, r, k}), all.erase((ODTNode) {l, r, k});
}

inline auto split(int x) {
	auto it = all.lower_bound((ODTNode) {x, 0, 0});

	if (it != all.end() && it->l == x)
		return it;

	--it;
	int l = it->l, r = it->r, k = it->k;
	remove(l, r, k), insert(l, x - 1, k);
	return insert(x, r, k);
}

inline int getpre(int x) {
	auto it = prev(all.upper_bound((ODTNode) {x, 0, 0}));

	if (it->l < x)
		return x - 1;

	auto cur = col[it->k].lower_bound((ODTNode) {x, 0, 0});
	return cur == col[it->k].begin() ? 0 : prev(cur)->r;
}

inline void assign(int l, int r, int k, int t) {
	auto itr = split(r + 1), itl = split(l);
	vector<int> vec;

	for (auto it = itl; it != itr; ++it) {
		if (it != itl)
			vec.emplace_back(it->l);

		auto nxt = col[it->k].upper_bound(*it);

		if (nxt != col[it->k].end())
			vec.emplace_back(nxt->l);

		col[it->k].erase(*it);
	}

	all.erase(itl, itr), insert(l, r, k);
	vec.emplace_back(l);
	auto nxt = col[k].upper_bound((ODTNode) {l, r, k});

	if (nxt != col[k].end())
		vec.emplace_back(nxt->l);

	for (auto it : vec) {
		nd[++tot] = (Node) {it, pre[it], t, -1, 0};
		pre[it] = getpre(it);
		nd[++tot] = (Node) {it, pre[it], t, 1, 0};
	}
}
} // namespace ODT

namespace BIT {
int c[N];

inline void update(int x, int k) {
	for (; x <= n; x += x & -x)
		c[x] += k;
}

inline int query(int x) {
	int res = 0;

	for (; x; x -= x & -x)
		res += c[x];

	return res;
}
} // namespace BIT

void cdq(int l, int r) {
	if (l == r)
		return;

	int mid = (l + r) >> 1;
	cdq(l, mid), cdq(mid + 1, r);
	sort(nd + l, nd + mid + 1, [](const Node &a, const Node &b) { return a.x < b.x; });
	sort(nd + mid + 1, nd + r + 1, [](const Node &a, const Node &b) { return a.x < b.x; });
	int j = l;

	for (int i = mid + 1; i <= r; ++i) {
		for (; j <= mid && nd[j].x <= nd[i].x; ++j)
			if (!nd[j].id)
				BIT::update(nd[j].y + 1, nd[j].k);

		if (nd[i].id > 0)
			ans[nd[i].id] += BIT::query(nd[i].y + 1);
		else if (nd[i].id < 0)
			ans[-nd[i].id] -= BIT::query(nd[i].y + 1);
	}

	for (--j; j >= l; --j)
		if (!nd[j].id)
			BIT::update(nd[j].y + 1, -nd[j].k);
}

signed main() {
	n = read(), m = read();

	for (int i = 1; i <= n; ++i) {
		a[i] = read();

		if (mp.find(a[i]) == mp.end())
			mp[a[i]] = ++cnt;

		a[i] = mp[a[i]], pre[i] = lst[a[i]], lst[a[i]] = i;
		nd[++tot] = (Node) {i, pre[i], 0, 1, 0};
		ODT::insert(i, i, a[i]);
	}

	for (int i = 1; i <= m; ++i) {
		if (read() == 1) {
			int l = read(), r = read(), k = read();

			if (mp.find(k) == mp.end())
				mp[k] = ++cnt;

			ODT::assign(l, r, mp[k], i);
		} else {
			int l = read(), r = read();
			nd[++tot] = (Node) {r, l - 1, i, 0, ++cntq};
			nd[++tot] = (Node) {l - 1, l - 1, i, 0, -cntq};
		}
	}

	sort(nd + 1, nd + tot + 1, [](const Node &a, const Node &b) { return a.t < b.t; });
	cdq(1, tot);

	for (int i = 1; i <= cntq; ++i)
		printf("%d\n", ans[i]);

	return 0;
}

P3206 [HNOI2010] 城市建设

给定一张图支持动态的修改边权,要求在每次修改边权之后输出这张图的最小生成树的最小代价和。

\(n \leq 2 \times 10^4\)\(m, q \leq 5 \times 10^4\)

考虑对时间进行分治,假设现在处理 \([l, r]\) ,那么将 \([l, r]\) 涉及到的边称为动态边,其余边称为静态边。

每层先用并查集将一定没用的静态边删去,从而简化了静态边集。

接下来考虑如何将 \([l, r]\) 拆分为 \([l, mid]\)\([mid + 1, r]\) ,可以发现另一个区间的动态边会变为静态边,于是动态边会不断的变成静态边,最后变成了一个纯静态的MST。

感觉这道题是cdq分治的扩展,需要处理左对右和右对左的贡献分别递归。

实现上有两个trick:

  • 若将动态边边权全设为 \(+\infty\) 跑一遍MST,此时不在MST中的边就可以直接删除了。
  • 若将动态边边权全设为 \(-\infty\) 跑一遍MST,此时在MST中的静态边递归时一定仍在MST中,因此可以直接将其统计贡献并将这些边所连接的连通块缩点。

第一个操作则保证边的规模与点同阶,第二个操作保证最多有 \(r - l\) 个点不会被缩,即点的规模是 \(r - l\) 的。

注意左区间对右区间和右区间对左区间的贡献处理有一点小差异。

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 2e4 + 7, M = 5e4 + 7, LOGN = 21;

struct DSU {
	stack<pair<int, int> > sta;

	int fa[N], siz[N];

	inline void prework(int n) {
		iota(fa + 1, fa + n + 1, 1);
		fill(siz + 1, siz + n + 1, 1);
	}

	inline int find(int x) {
		while (x != fa[x])
			x = fa[x];

		return x;
	}

	inline void merge(int x, int y) {
		x = find(x), y = find(y);

		if (x == y)
			return;

		if (siz[x] < siz[y])
			swap(x, y);

		siz[x] += siz[y], fa[y] = x;
		sta.emplace(x, y);
	}

	inline void cancel() {
		int x = sta.top().first, y = sta.top().second;
		sta.pop(), fa[y] = y, siz[x] -= siz[y];
	}

	inline void clear(int s = 0) {
		while (sta.size() > s)
			cancel();
	}
} dsu, dsu1;

struct Edge {
	int u, v, w, tag;

	inline Edge(int _u = 0, int _v = 0, int _w = 0, int _tag = 0) : u(_u), v(_v), w(_w), tag(_tag) {}

	inline bool operator < (const Edge &rhs) const {
		return w < rhs.w;
	}
} e[M];

struct Update {
	int x, k;
} upd[M];

vector<Edge> stc[LOGN], dmc;

ll ans[M], res[LOGN];
int tim[LOGN];
bool vis[M];

int n, m, q;

template <class T = int>
inline T read() {
	char c = getchar();
	bool sign = (c == '-');
	
	while (c < '0' || c > '9')
		c = getchar(), sign |= (c == '-');
	
	T x = 0;
	
	while ('0' <= c && c <= '9')
		x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	
	return sign ? (~x + 1) : x;
}

inline void pushdown(int d) {
	vector<Edge> vec = stc[d];
	sort(vec.begin(), vec.end());

	for (Edge &it : vec) { // useless edge
		if (dsu1.find(it.u) == dsu1.find(it.v))
			it.tag = -1;
		else
			dsu1.merge(it.u, it.v);
	}

	dsu1.clear();

	for (Edge it : dmc)
		dsu1.merge(it.u, it.v);

	dmc.clear(), res[d + 1] = res[d];

	for (Edge &it : vec) { // essential edge
		if (it.tag == -1 || dsu1.find(it.u) == dsu1.find(it.v))
			continue;

		dsu1.merge(it.u, it.v), dsu.merge(it.u, it.v);
		it.tag = 1, res[d + 1] += it.w;
	}

	dsu1.clear(), stc[d + 1].clear();

	for (Edge it : vec)
		if (!it.tag && dsu.find(it.u) != dsu.find(it.v))
			stc[d + 1].emplace_back(dsu.find(it.u), dsu.find(it.v), it.w);
}

void cdq(int l, int r, int d) {
	tim[d] = dsu.sta.size();

	if (l == r) {
		stc[d].emplace_back(dsu.find(e[upd[l].x].u), dsu.find(e[upd[l].x].v), upd[l].k);
		e[upd[l].x].w = upd[l].k;
		pushdown(d);
		ans[l] = res[d + 1];
		return;
	}

	int mid = (l + r) >> 1;

	for (int i = l; i <= mid; ++i) { // update -> dymanic
		dmc.emplace_back(dsu.find(e[upd[i].x].u), dsu.find(e[upd[i].x].v));
		vis[upd[i].x] = true;
	}

	for (int i = mid + 1; i <= r; ++i) // dynamic -> static
		if (!vis[upd[i].x])
			stc[d].emplace_back(dsu.find(e[upd[i].x].u), dsu.find(e[upd[i].x].v), e[upd[i].x].w);

	pushdown(d);

	for (int i = mid + 1; i <= r; ++i)
		if (!vis[upd[i].x])
			stc[d].pop_back();

	for (int i = l; i <= mid; ++i)
		vis[upd[i].x] = false;

	cdq(l, mid, d + 1), dsu.clear(tim[d]);

	for (Edge &it : stc[d])
		it.tag = 0;

	for (int i = mid + 1; i <= r; ++i)
		vis[upd[i].x] = true;

	for (int i = l; i <= mid; ++i) // dynamic -> static
		if (!vis[upd[i].x])
			stc[d].emplace_back(dsu.find(e[upd[i].x].u), dsu.find(e[upd[i].x].v), e[upd[i].x].w);

	for (int i = mid + 1; i <= r; ++i) { // update -> dymanic
		dmc.emplace_back(dsu.find(e[upd[i].x].u), dsu.find(e[upd[i].x].v));
		vis[upd[i].x] = false;
	}

	pushdown(d);
	cdq(mid + 1, r, d + 1), dsu.clear(tim[d]);
}

signed main() {
	n = read(), m = read(), q = read();

	for (int i = 1; i <= m; ++i)
		e[i].u = read(), e[i].v = read(), e[i].w = read();

	for (int i = 1; i <= q; ++i) {
		upd[i].x = read(), upd[i].k = read();
		vis[upd[i].x] = true;
		dmc.emplace_back(e[upd[i].x]);
	}

	for (int i = 1; i <= m; ++i)
		if (!vis[i])
			stc[1].emplace_back(e[i]);

	for (int i = 1; i <= q; ++i)
		vis[upd[i].x] = false;

	dsu.prework(n), dsu1.prework(n);
	cdq(1, q, 1);

	for (int i = 1; i <= q; ++i)
		printf("%lld\n", ans[i]);


	return 0;
}
posted @ 2024-08-02 14:38  我是浣辰啦  阅读(1)  评论(0编辑  收藏  举报