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 \to (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 \to [i, pos)\) 的所需步數之和 . 那么每次詢問就能用差分來表示成 \(Calc(l,pos) - Calc(r + 1, pos)\) 了 .

然后倍增的時候類似於這樣跳的 :

img

假設我們總共要經過的是 紅色 那一段(其中 \(l_{pos}\) 已經跳完了) , 每次走的是 粉色 那一段 .

發現我們每次走的時候 , 要記下前面走了多少步數 , 然后給答案加上這一段的貢獻 \(len \times tot\) .

最后有一小段多余的 藍紫色 (因為每次 \(2^j\) 覆蓋的是所有步數為這么多的 , 最后可能不滿) 這段貢獻就是 \(len \times (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\n", ans / g, len / g);
	}

    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM