loj6435. 「PKUSC2018」星际穿越

题意

略。

题解

对于做这题的感觉:好难啊,好妙啊,我好菜啊。
首先,需要明确一个结论,就是一个点\(x\)到另一个\(y\)\(x > y\))有且仅有两种可能最优的走法,要么一直向左走,要么向右走一步后一直向左走。
暴力一点的话我们可以通过\(\mathcal O(n ^ 2)\)的dp进行预处理和\(\mathcal O(n)\)进行单次询问。
其中dp数组要处理出\(f_{i, j}\)代表点\(i\)\(j\)步,能走到\(i\)左边的最左的点(隐含着\([f_{i, j}, i]\)这段区间的点从\(i\)开始走不超过\(j\)步都能走到)。
如何优化?能否用倍增来完成?好像有点困难。
但是,我们可以令取状态,令\(f_{i, j}\)代表区间\([i, n]\)里的点走\(2 ^ j\)步,能走到\(i\)左边最左的点。
这样,转移即为

\[f_{i, 0} = \max(f_{i + 1, 0}, l_i) \\ f_{i, j} = f_{f_{i, j - 1} \ \ , j - 1} \\ \]

但是这样子还是无法优化询问复杂度。
考虑令函数\(C(l, x)\)\(x\)\([l, x)\)内的所有点的总步数,则一次询问的和就是\(C(l, x) - C(r + 1, x)\)
如何计算\(C(l, x)\)
考虑先从\(x\)处走一步,花费代价为1,可以走到\([l_x, n]\)中的所有点。
这时候再走\(2 ^ i\)步,可以走到区间\([f_{l_x, 2 ^ i}, n]\)中的任意一点。
我们还可以发现一些结论:离\(x\)越远的点需要的步数越多(单调性),因此我们可以通过二分来寻找分段点。
为了这个,我们要预处理\(s_{x, i}\)表示点\(x\)\([f_{x, i}, x]\)这些点需要的向左走的总步数,则

\[s_{i, 0} = i - f_{i, 0} \\ s_{i, j} = s_{i, j - 1} + s_{f_{i, j - 1} \ \ , j - 1} + 2 ^ {j - 1} * (f_{i, j - 1} - f_{i, j}) \\ \]

询问时,按最基本的想法可以对于每个点,做一个二进制拆分,然后把所有贡献加起来。
但是发现对于整段区间\([l, x - 1]\)的点,可以同时进行二进制拆分(因为具有单调性),这里就要用到预处理的\(s\)数组。
复杂度\(\mathcal O((n + q) \log_2 n)\)

#include <bits/stdc++.h>
#define fi first
#define se second
using namespace std;
typedef long long ll;

const int N = 3e5 + 5, M = 19;
int n, q, arr[N];
int f[M][N]; ll s[M][N];
int lg2 (int x) {
	return 32 - __builtin_clz(x);
}
ll solve (int o, int x) {
	if (arr[x] <= o) {
		return x - o;
	}
	int co = 1; ll ret = x - arr[x]; x = arr[x];
	for (int i = M - 1; ~i; --i) {
		if (f[i][x] > o) {
			ret += s[i][x] + 1ll * co * (x - f[i][x]);
			x = f[i][x], co += 1 << i;
		}
	}
	return ret + 1ll * (co + 1) * (x - o);
}
int main () {
	scanf("%d", &n), arr[1] = 1;
	for (int i = 2; i <= n; ++i) {
		scanf("%d", &arr[i]);
	}
	f[0][n] = arr[n], s[0][n] = n - f[0][n];
	for (int i = n - 1; i; --i) {
		f[0][i] = min(f[0][i + 1], arr[i]), s[0][i] = i - f[0][i];
	}
	for (int j = 1; j < lg2(n); ++j) {
		for (int i = n; i; --i) {
			if (f[j - 1][i]) {
				f[j][i] = f[j - 1][f[j - 1][i]];
				s[j][i] = s[j - 1][i] + s[j - 1][f[j - 1][i]] + (1ll << (j - 1)) * (f[j - 1][i] - f[j - 1][f[j - 1][i]]);
			}
		}
	}
	scanf("%d", &q);
	for (int i = 1, l, r, x; i <= q; ++i) {
		scanf("%d%d%d", &l, &r, &x);
		ll ouo = solve(l, x) - solve(r + 1, x), ovo = r - l + 1, g = __gcd(ouo, ovo);
		printf("%lld/%lld\n", ouo / g, ovo / g);
	}
	return 0;
}
posted @ 2019-12-10 15:00  psimonw  阅读(210)  评论(0编辑  收藏  举报