zoukankan      html  css  js  c++  java
  • 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))。用分数表示。

    数据范围:(1leq n, qleq 3 imes 10^5)

    本题题解

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

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

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

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

    考虑第二步:

    • 向左可以走到 (min_{zin[l_x, x -1] ext{ 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) = egin{cases} 1 && ygeq l_x\ min_{l_xleq zleq 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 (jgeq 2))

    可以发现一个事实:(t(i, x + y) = t(t(i, x), y)) ((x, ygeq 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 (jgeq 1))

    利用 (f) 数组,可以快速回答一个 (mathrm{dist}(x, y))。首先特判 (ygeq l_x)。然后转化为求 (l_x) 或其右边任意点,到 (y) 的最短路。做法:从 (lfloorlog_2(n) floor)(0)(从大到小)枚举 (2) 的次幂 (j)。如果从当前的 (x)(2^j) 步,到达的点 (f(x, j) > y),则走过去((xgets 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))。做法:首先特判 (ygeq 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;
    }
    
  • 相关阅读:
    Python Day7(相关补充)
    Python Day7
    Python Day6
    Python Day5
    Python Day4
    Python Day3
    Python Day2
    Python Day1
    复杂装饰器原理分析
    Unity 坐标
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/14498848.html
Copyright © 2011-2022 走看看