zoukankan      html  css  js  c++  java
  • LOJ #6435. 「PKUSC2018」星际穿越(倍增)

    题面

    LOJ#6435. 「PKUSC2018」星际穿越

    题解

    **参考了 这位大佬的博客 **

    这道题好恶心啊qwq~~

    首先一定要认真阅读题目 !! 注意 (l_i<r_i<x_i) 这个条件 !!

    所以它询问的就是向左走的最短路了 .

    不难发现只有两种策略 , 要么一直向左走 ; 要么第一次向右走 , 然后一直向左走 .

    并且到一个定点 (x) 的最短路长度 肯定是从右向左一段段递增的 .

     为什么呢 ? 不难发现 如果向右走两次 , 那么只有一次是一定有效的 , 另外一次的 (l_i) 一定不会小于这次 .

    向左走的话 , 每次就记下沿途的 (l_i) 的最小值 , 用这个去转移走 (j) 次时 (l) 的最小值就行了 . ( (70pts) 见我 (LOJ) 提交吧qwq .)

    然后这样暴力做的话就是 (O(n^2)) 的复杂度 显然不行 .

    考虑优化 , 发现这个是一段段的 且 有连续性 , 有一个神奇的倍增可以快速实现这个功能 .

    (f_{i, j})([i, n]) 所有点走 (2^j) 次能到达的最左端点 .

    [displaystyle f_{i,j} = f_{f_{i,j-1},j-1} ]

    为什么要这样记呢 ? 因为这样可以同时统计第一次向右走可能产生的贡献 .

    (sum_{i,j})(i o (i sim f_{i,j})) 中所有点走的步数之和 . 这个转移就很显然啦 .

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

    然后我们考虑走的时候算答案 . 因为我们一开始预处理只包括了可能向右走的情况 , 但直接第一步向左走的没有处理掉 .

    此处我们单独处理第一步向左走的情况就行了 .

    (Calc(i, pos))(i o [i, pos)) 的所需步数之和 . 那么每次询问就能用差分来表示成 (Calc(l,pos) - Calc(r + 1, pos)) 了 .

    然后倍增的时候类似于这样跳的 :

    img

    假设我们总共要经过的是 红色 那一段(其中 (l_{pos}) 已经跳完了) , 每次走的是 粉色 那一段 .

    发现我们每次走的时候 , 要记下前面走了多少步数 , 然后给答案加上这一段的贡献 (len imes tot) .

    最后有一小段多余的 蓝紫色 (因为每次 (2^j) 覆盖的是所有步数为这么多的 , 最后可能不满) 这段贡献就是 (len imes (tot + 1)) .

    代码好像很短 ?

    代码

    #include <bits/stdc++.h>
    #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
    #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
    #define Set(a, v) memset(a, v, sizeof(a))
    #define debug(a) cout << #a << ": " << a << endl
    using namespace std;
    
    inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
    inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;}
    
    inline int read() {
        int x = 0, fh = 1; char ch = getchar();
        for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
        for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
        return x * fh;
    }
    
    void File() {
    #ifdef zjp_shadow
    	freopen ("6435.in", "r", stdin);
    	freopen ("6435.out", "w", stdout);
    #endif
    }
    
    const int N = 3e5 + 1e3, inf = 0x3f3f3f3f;
    
    int L[N], n, f[N][21], sum[N][21], Log2[N];
    
    void Init() {
    	f[n + 1][0] = inf;
    	Fordown (i, n, 1)
    		f[i][0] = min(f[i + 1][0], L[i]), sum[i][0] = i - f[i][0];
    	For (j, 1, Log2[n]) For (i, 1, n) if (f[i][j - 1]) {
    		f[i][j] = f[f[i][j - 1]][j - 1];
    		sum[i][j] = sum[i][j - 1] + sum[f[i][j - 1]][j - 1] + ((f[i][j - 1] - f[i][j]) << (j - 1));
    	}
    }
    
    inline int Calc(int qp, int pos) {
    	if (L[pos] <= qp) return pos - qp;
    	int res = pos - L[pos]; pos = L[pos]; int tot = 1;
    	Fordown (i, Log2[pos], 0)
    		if (f[pos][i] > qp) res += sum[pos][i] + (pos - f[pos][i]) * tot, pos = f[pos][i], tot += 1 << i;
    	return res + (pos - qp) * (tot + 1);
    }
    
    int main () {
    	File();
    
    	n = read(); L[1] = 1; For (i, 2, n) L[i] = read(), Log2[i] = Log2[i >> 1] + 1; Init();
    
    	int m = read();
    	For (i, 1, m) {
    		int l = read(), r = read(), x = read();
    		int ans = Calc(l, x) - Calc(r + 1, x), len = r - l + 1, g = __gcd(ans, len);
    		printf ("%d/%d
    ", ans / g, len / g);
    	}
    
        return 0;
    }
    
  • 相关阅读:
    第五次作业
    第四次作业
    第三次作业
    第二次作业
    第一次作业
    实验三 算术编码压缩方法
    实验二 统计压缩方法的具体实现
    实验一 建立统计压缩方法理论模型
    第五次作业
    第四次作业
  • 原文地址:https://www.cnblogs.com/zjp-shadow/p/9186437.html
Copyright © 2011-2022 走看看