1. 程式人生 > 其它 >[牛客82B, 一起來做題第一場E]區間的連續段 (倍增,思維)

[牛客82B, 一起來做題第一場E]區間的連續段 (倍增,思維)

首先考慮最暴力的做法。那複雜度是q * range的顯然會爆。

原本我是想用什麼資料結構優化這個過程,但是想了想好像沒什麼東西處理劃分這個操作。

於是考慮觀察這個操作的性質,我們發現對於每個範圍,一旦L確定那麼,在[L, R] 之間的劃分數其實是固定的。(但是劃分方案可能會有很多種)

這裡可以稍稍證明一下:

假設區間為[L, R],那麼最多的劃分數就是R-L+1,之後我們為了儘可能減少劃分數,我們必然要儘可能讓一個劃分集合與左右兩邊的集合合併。

於是我們就利用貪心的思想,儘量讓每一個劃分集合都儘量大,那麼我們就能做到劃分數最小。

於是我們解決了第一步,如何處理劃分數的問題。但是我們不能通過預處理 divide_num[L][R]這個二維陣列去得到答案。

於是考慮優化掉陣列第二維,該如何快速知道在L確定的情況下,到R的劃分數呢?

我們發現,最最開始,我們的劃分陣列存的其實都是到下一個劃分點的位置,我們每次可以通過跳躍,去獲得下一個劃分點的位置,從而獲得答案。

但是最壞情況下就是,這個跳躍數都為1,即每個a[i] + a[i+1] > k, a[i] <= k。這樣的話就會被卡成n2了。我們發現這個跳躍操作,其實是可以被優化的。

考慮到跟樹上倍增一樣的思想,每次跳2的冪次,這樣的話我們的複雜度就被降低到nlogn了。

於是開始寫。

程式碼如下:

#include <bits/stdc++.h>
using namespace
std; typedef long long ll; typedef pair<int, int> pii; const int inf = 0x3f3f3f3f; ///1061109567 const int maxn = 2e6 + 10; int n, m, k; ll sum[maxn], a[maxn]; int f[maxn][30], cant[maxn]; int main() { scanf("%d%d%d", &n, &m, &k); for (int i = 1; i <= n; ++ i) { scanf(
"%lld", &a[i]); sum[i] = a[i] + sum[i-1]; cant[i] = cant[i-1] + (a[i] > k); } for (int j = 0; j <= 20; ++ j) { for (int i = 1; i <= n; ++ i) { if (j == 0) { int pos = upper_bound(sum+1, sum+1+n, sum[i-1]+k) - sum; f[i][j] = pos; //cout << "pos: " << pos << endl; } else f[i][j] = f[f[i][j-1]][j-1]; } } while (m--) { int l, r; scanf("%d%d", &l, &r); if (cant[r] - cant[l-1] > 0) { puts("Chtholly"); continue; } int ans = 1; ///至於ans為什麼一開始是1,是因為我們記錄的都是下一個劃分數的位置, ///並且在尋找答案的時候讓下一個劃分數的位置<=R這說明最後一條我們是沒有算在答案裡面的, ///所以需要+1。 for (int j = 20; j >= 0; -- j) { ///f[l][j] != 0的原因是因為l + 1<<j會大於n, ///這樣的話倍增陣列其實是沒有給值的此時為0 ///0 <= r 但是我們又不希望訪問越界的位置。所以需要!=0 if (f[l][j] && f[l][j] <= r) { l = f[l][j]; ans += (1 << j); } } // puts(""); printf("%d\n", ans); } return 0; }