• 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;
    }
    
  • 相关阅读:
    MySQL数据库之WHERE条件语句
    MySQL数据库的简单操作指令之DML语言
    课程设计必备之数据库操作代码模板
    MySQL数据库之外键约束的简单理解
    pdf.js的使用
    javascript连连看
    数据库问题
    列表文字超出后,鼠标悬浮显示全部内容
    springboot component注入servecie
    点击列表 获取table tr td 下的input value
  • 原文地址:https://www.cnblogs.com/psimonw/p/12016282.html
Copyright © 2020-2023  润新知