LOJ6435 「PKUSC2018」星际穿越

LOJ6435 「PKUSC2018」星际穿越

题目大意

题目链接

一张 \(n\) 个点的无向图,从第 \(i\) 个点向 \([l_i, i - 1]\) 中所有点连无向边,边的长度均为 \(1\)。记 \(\mathrm{dist}(i, j)\) 表示从点 \(i\) 到点 \(j\) 的最短路长度。\(q\) 次询问,每次给出 \(l, r, x\)(保证 \(l < r < x\)),求 \(\frac{1}{r - l + 1}\sum_{y = l}^{r}\mathrm{dist}(x, y)\)。用分数表示。

数据范围:\(1\leq n, q\leq 3\times 10^5\)

本题题解

先考虑如何快速回答一个 \(\mathrm{dist}(x, y)\) (\(x > y\))。

如果 \(y\geq l_x\),答案显然是 \(1\),特判这种情况。

否则我们至少会走两步。考虑第一步:

  • 向左可以走到 \([l_x, x - 1]\) 里所有点。
  • 向右可以走到所有的 \(z\),满足 \(l_z\leq x < z\)

考虑第二步:

  • 向左可以走到 \(\min_{z\in[l_x, x -1]\text{ or } l_z \leq x < z}\{l_z\}\)。注意到这个值一定是 \(< x\) 的。所以我们把 \(x < l_z < z\)\(z\) 算进去也不影响结果。即原式等于 \(\min_{z = l_x}^{n}\{l_z\}\)。换句话说,原问题等价于:第一步可以到达右边任何一个点。且有一个推论:此后再也不需要向右走

于是我们完成了一个初步转化,从求 \(x\)\(y\) 的距离,变成了求 \(l_x\) 及其右边所有点到 \(y\) 的距离的最小值。形式化地说:

\[\mathrm{dist}(x, y) = \begin{cases} 1 && y\geq l_x\\ \min_{l_x\leq z\leq n}\{\mathrm{dist}(z, y)\} + 1 && y < l_x \end{cases} \]

\(t(i, j)\) 表示从 \(i\)\(i\) 右边任意一个点出发,走 \(j\) 步,能到达的最左边的点(隐含之意是它右边的点都能在 \(j\) 步以内到达)。则:

  • \(t(i, 1) = \min_{k = i}^{n}\{l_k\}\)
  • \(t(i, j) = \min_{k = t(i, j - 1)}^{n}\{l_k\}\quad (j\geq 2)\)

可以发现一个事实:\(t(i, x + y) = t(t(i, x), y)\) (\(x, y\geq 1\))。结合实际意义非常好理解,也就是任何一种走 \(x + y\) 步的方案,总会经历先走 \(x\) 步,再走 \(y\) 步。

于是考虑倍增。设 \(f(i, j) = t(i, 2^{j})\),也就是从 \(i\) 或其右边任意一个点出发,走 \(2^{j}\) 步,能到达的最左边的点。那么:

  • \(f(i, 0)=\min_{k = i}^{n}\{l_k\}\)
  • \(f(i, j) = f(f(i, j - 1), j - 1)\quad (j\geq 1)\)

利用 \(f\) 数组,可以快速回答一个 \(\mathrm{dist}(x, y)\)。首先特判 \(y\geq l_x\)。然后转化为求 \(l_x\) 或其右边任意点,到 \(y\) 的最短路。做法:从 \(\lfloor\log_2(n)\rfloor\)\(0\)(从大到小)枚举 \(2\) 的次幂 \(j\)。如果从当前的 \(x\)\(2^j\) 步,到达的点 \(f(x, j) > y\),则走过去(\(x\gets f(x, j)\)),并令答案加上 \(2^{j}\)。最终得到的位置 \(x\) 一定距离 \(y\) 恰好为 \(1\)(类似于倍增法求 LCA 时,停在的位置恰好是 LCA 的儿子)。

但这只是求一对 \(\mathrm{dist}(x, y)\),题目要求 \(x\) 到一段区间的距离和。设 \(D(x, y)\) 表示 \(x\)\([y, x - 1]\) 里所有点的距离和,则答案等于 \(D(x, l) - D(x, r + 1)\)。可以分别计算。

用一个数组记录从 \(i\) 或其右边任意点到 \([f(i, j), i - 1]\) 里所有点的距离和。即 \(s(i, j) = \sum_{k = f(i, j)}^{i - 1} \min_{u = i}^{n}\mathrm{dist}(u, k)\)。它的转移是:\(s(i, j) = s(i, j - 1) + s(f(i, j - 1), j - 1) + (f(i, j - 1)-f(i, j))\cdot 2^{j - 1}\)

利用 \(s\) 数组,可以快速回答一个 \(D(x, y)\)。做法:首先特判 \(y\geq l_x\)。然后分 \([l_x, x - 1]\)\([y, l_x)\) 里的点两类分别算。前者答案显然是 \(x - l_x\)。后者可以用上述的倍增法求出。

时间复杂度 \(\mathcal{O}((n + q)\log n)\)

参考代码

// problem: LOJ6435
#include <bits/stdc++.h>
using namespace std;

#define mk make_pair
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }

const int MAXN = 3e5;
const int LOG = 18;

ll gcd(ll x, ll y) { return (!y) ? x : gcd(y, x % y); }

int n, l[MAXN + 5];
int f[MAXN + 5][LOG + 1];
ll s[MAXN + 5][LOG + 1];

ll calc(int x, int y) {
	assert(x >= y);
	if (y >= l[x]) {
		return x - y;
	}
	
	ll res = x - l[x];
	x = l[x];
	
	int dis = 1;
	for (int j = LOG; j >= 0; --j) {
		if (f[x][j] > y) {
			res += (ll)dis * (x - f[x][j]) + s[x][j];
			dis += (1 << j);
			x = f[x][j];
		}
	}
	res += (ll)(dis + 1) * (x - y);
	return res;
}
int main() {
	cin >> n;
	multiset<int> minl;
	for (int i = 2; i <= n; ++i) {
		cin >> l[i];
		minl.insert(l[i]);
	}
	
	for (int i = 2; i <= n; ++i) {
		
		f[i][0] = *minl.begin(); // 右边最小的
		s[i][0] = (i - f[i][0]);
		minl.erase(minl.find(l[i]));
		
		for (int j = 1; j <= LOG; ++j) {
			f[i][j] = f[f[i][j - 1]][j - 1];
			s[i][j] = s[i][j - 1] + s[f[i][j - 1]][j - 1] + (ll)(f[i][j - 1] - f[i][j]) * (1 << (j - 1));
		}
	}
	
	int q; cin >> q;
	for (int tq = 1; tq <= q; ++tq) {
		int l, r, x;
		cin >> l >> r >> x;
		
		ll fz = calc(x, l) - calc(x, r + 1);
		ll fm = r - l + 1;
		ll g = gcd(fz, fm);
		fz /= g;
		fm /= g;
		cout << fz << "/" << fm << endl;
	}
	return 0;
}
posted @ 2021-03-08 11:44  duyiblue  阅读(136)  评论(2编辑  收藏  举报