题解:P17063 [JRKSJ R10 热身赛] Nelumbo nucifera

题很有意思!赞美出题人!

\(n\) 种颜色分块,块长设为 \(B\)

预处理 \(p_1(i)\) 表示该节点到根节点的路径上,包含除了第 \(i\) 块以外的所有颜色,每种颜色恰好出现一次;\(p_2(i,j)\) 表示该节点到根节点的路径上,包含除了第 \(i,j\) 块以外的所有颜色,每种颜色恰好出现一次。

查询 \(x,y\) 时,设 \(x,y\) 所在的块分别为 \(b_x,b_y\)。若 \(b_x=b_y\),则从 \(p_1(b_x)\) 开始接上块内的其他颜色;若 \(b_x\neq b_y\),则从 \(p_2(b_x,b_y)\) 开始接上两个块内的其他颜色。

暴力预处理的次数是 \(\mathcal{O}\left(\dfrac{n^3}{B^2}\right)\),查询的总次数是 \(\mathcal{O}(nB)\)。取 \(B=n^{2/3}\) 可以做到 \(\mathcal{O}(n^{5/3})\) 次。

瓶颈在于预处理,考虑优化 \(p_2\) 的次数。仿照缺一分治,分治到 \([l,r]\) 表示节点 \(u\) 到根节点的路径上,包含除了 \([l,r]\) 块以外的所有颜色,每种颜色恰好出现一次。若两个块都在 \([l,mid]\) 内,就把 \([mid+1,r]\) 块内的颜色加到 \(u\) 中递归下去;两个块都在 \([mid+1,r]\) 内同理;若一个块在 \([l,mid]\) 内,另一个块在 \([mid+1,r]\) 内,可以在 \([l,mid]\)\([mid+1,r]\) 内依次分治。具体来说,分治时维护两个区间 \([l_1,r_1]\)\([l_2,r_2]\),先对左区间折半,再对右区间折半……这样交替分治下去,直到两个区间的长度都为 \(1\)。每次折半时都把另一侧的颜色加到 \(u\) 上。

分析操作次数。对于交替分治,设 \(T_1(n)\) 表示左右两个分治区间的长度都为 \(n\) 时,需要添加多少个块,则

\[\begin{align*} T_1(1)&=0\\ T_1(n)&=4T_1\left(\dfrac n2\right)+3n \end{align*} \]

解得 \(T_1(n)=3n^2-3n\)

对于普通的分治,设 \(T_2(n)\) 表示当前分治区间的长度为 \(n\) 时,需要添加多少个块,则

\[\begin{align*} T_2(2)&=0\\ T_2(n)&=2T_2\left(\dfrac n2\right)+n+T_1\left(\dfrac n2\right) \end{align*} \]

\(n=2^k\),则 \(T_2(n)=\dfrac{3n^2-(k+5)n}{2}\)

于是我们把 \(p_2\) 的预处理优化到了 \(T_2\left(\dfrac nB\right)B=\mathcal{O}\left(\dfrac{n^2}{B}\right)\) 次。\(p_1(i)\) 也可以从 \(p_2(i,j)\) 出发得到,操作次数为 \(\mathcal{O}(n)\)

查询部分次数不变,取 \(B=n^{1/2}\),可以做到 \(\mathcal{O}(n^{3/2})\) 次。可以通过本题。

代码
#include <bits/stdc++.h>

using namespace std;

using ll = long long;
using i128 = __int128;
using ui = unsigned int;
using ull = unsigned long long;
using u128 = unsigned __int128;
using ld = long double;
using pii = pair<int, int>;
const int MAXN = 5e4 + 5;
const int LEN = 200, MAX_CNT = (MAXN - 5) / LEN + 5;

template<typename T> T lowbit(T x) { return x & -x; }
template<typename T> void chkMin(T &x, T y) { x = y < x ? y : x; }
template<typename T> void chkMax(T &x, T y) { x = x < y ? y : x; }
constexpr int lg2(ll x) { return 63 ^ __builtin_clzll(x); }
constexpr ll bitCeil(ll x) { return x == 1 ? 1ll : 1ll << lg2(x - 1) + 1; }

int n, len;
int p1[MAX_CNT], p2[MAX_CNT][MAX_CNT];
bool flag;
int blCnt, blNum[MAXN], lb[MAX_CNT], rb[MAX_CNT];

int add(int id, int col);

void solve1(int p, int l1, int r1, int l2, int r2, int tp) {
	if (l1 == r1 && l2 == r2) {
		p2[l1][l2] = p;
		return;
	}
	if (!tp) {
		int mid = l1 + r1 >> 1;
		int u = p;
		for (int i = lb[mid + 1]; i <= rb[r1]; ++i) u = add(u, i);
		solve1(u, l1, mid, l2, r2, tp ^ 1);
		u = p;
		for (int i = lb[l1]; i <= rb[mid]; ++i) u = add(u, i);
		solve1(u, mid + 1, r1, l2, r2, tp ^ 1);
	} else {
		int mid = l2 + r2 >> 1;
		int u = p;
		for (int i = lb[mid + 1]; i <= rb[r2]; ++i) u = add(u, i);
		solve1(u, l1, r1, l2, mid, tp ^ 1);
		u = p;
		for (int i = lb[l2]; i <= rb[mid]; ++i) u = add(u, i);
		solve1(u, l1, r1, mid + 1, r2, tp ^ 1);
	}
}

void solve2(int p, int l, int r) {
	if (r - l + 1 == 2) {
		p2[l][r] = p;
		return;
	}
	int mid = l + r >> 1;
	int u = p;
	for (int i = lb[mid + 1]; i <= rb[r]; ++i) u = add(u, i);
	solve2(u, l, mid);
	u = p;
	for (int i = lb[l]; i <= rb[mid]; ++i) u = add(u, i);
	solve2(u, mid + 1, r);
	solve1(p, l, mid, mid + 1, r, 0);
}

void init(int n_) {
	n = n_;
	for (int i = 1, j = 1; j <= n; ++i) {
		lb[i] = j;
		rb[i] = min(j + LEN - 1, n);
		while (j <= rb[i]) blNum[j++] = i;
	}
	blCnt = blNum[n];
	if (blCnt >= 2) {
		int mx = bitCeil(blCnt);
		for (int i = blCnt + 1; i <= mx; ++i) {
			lb[i] = n + 1;
			rb[i] = n;
		}
		solve2(1, 1, mx);
		for (int i = 1; i <= blCnt; ++i) {
			int j = i == blCnt ? 1 : i + 1, u = p2[min(i, j)][max(i, j)];
			for (int k = lb[j]; k <= rb[j]; ++k) u = add(u, k);
			p1[i] = u;
		}
	} else {
		p1[1] = 1;
	}
}

int solve(int n, int x, int y) {
	if (!flag) {
		flag = true;
		init(n);
	}
	int bx = blNum[x], by = blNum[y];
	if (bx > by) swap(bx, by);
	if (bx == by) {
		int u = p1[bx];
		for (int i = lb[bx]; i <= rb[bx]; ++i)
			if (i != x && i != y)
				u = add(u, i);
		return u;
	} else {
		int u = p2[bx][by];
		for (int i = lb[bx]; i <= rb[bx]; ++i)
			if (i != x && i != y)
				u = add(u, i);
		for (int i = lb[by]; i <= rb[by]; ++i)
			if (i != x && i != y)
				u = add(u, i);
		return u;
	}
}
posted @ 2026-07-04 22:41  P2441M  阅读(12)  评论(0)    收藏  举报