zoukankan      html  css  js  c++  java
  • 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
    ", ouo / g, ovo / g);
    	}
    	return 0;
    }
    
  • 相关阅读:
    自学Python3.5-字符串格式化 作用域 递归
    自学Python3.2-函数分类(内置函数)
    自学Python3.1-函数基础
    自学Python2.7-collections系列
    自学Python2.6-深浅拷贝
    自学Python2.5-基本数据类型-set集合
    自学Python2.4-基本数据类型-字典dict(objct)
    自学Python2.3-基本数据类型-元组tuple(object) 方法
    自学Python2.2-基本数据类型-列表list(object)
    java通过jdbc访问mysql,update数据返回值的思考
  • 原文地址:https://www.cnblogs.com/psimonw/p/12016282.html
Copyright © 2011-2022 走看看