--- tags: 程式筆記 --- # [DP學習筆記2] Codeforces 1736C Good Subarrays (Easy Version) > https://codeforces.com/contest/1736/problem/C1 ## 題意 在一個整數序列 $a$ 中,有 $n$ 個數字(從$1$開始編號至$n$),如:`2 1 4 3`,我們想要找出有幾個好區間? 首先要定義區間,對於區間來說有兩個特徵,一個左邊界 $l$ ,一個右邊界 $r$ ($1 \le l \le r \le n$),這兩個邊界代表 $[a_l, ..., a_r]$ 的整數序列,像是左邊界 $l = 2$,右邊界 $r = 4$,就代表 `1 4 3` 的整數序列。 接下來就是本題的重點了,好的區間是什麼?每個區間會對應到一個整數序列 $b$,這個 $b$ 中每個數字都要大於等於他的位置編號($i \le b_i$),像是剛剛的 `1 4 3` 就是一個好的區間,因為 $1 \le 1, 2 \le 4, 3 \le 3$,但是像是區間 $l=1, r=2$ 對應到的序列為 `2 1`,這就不是一個好的區間,因為第二個數字為 $1$,位置編號大於數字。 $$ 1 \le n \le 2 * 10^5 \\ 1 \le a_i \le n $$ **input** `4` `2 1 4 3` **output** `7` **說明** `[1,1], [2,2], [2,3], [2,4], [3,3], [3,4], [4,4]` 共 7 個好區間。 **備註** $[l, r]$ 是閉區間的表示法。 ## 思考 **前言** 我覺得直覺既美麗又飄渺,順著直覺走通常都會很開心,但是最困擾競技選手的就是直覺總是那麼無理取鬧,有時候哭有時笑,有時偏偏不來到,所以我認為一個好的 SOP 很重要,當有直覺的時候再回來思考當前的 SOP 是否有改進的必要。 (我是押韻大師 ^^) 首先盤點已知的線索與問題,遇到「區間」的問題時,有很高的機率區間與另一個區間有關聯,這個**關聯性往往是我們減少重複計算**的關鍵,然而減少重複計算的能力就是選手的核心能力之一,畢盡人類社會資源有限,使用最少的資源做最多的事情一直都是人類生存的課題~ **區間關聯尋找常用 SOP** 因為區間有兩個特徵,一個左邊界 $l$ ,一個右邊界 $r$,所以我們要來探討區間與區間之間的關聯,在探討區間與區間的關聯時,我們可以思考以下三種關聯,固定左變動右、固定右變動左或是固定長度變動左右。從這兩個特徵下去思考關聯會是很自然的思考流程。 **重返案發現場** 首先我們先整理題目中的線索 - 區間長度為 $1$ 的都是好區間,因為每個數字都大於等於 $1$。 - 好區間中,每個數字都要大於等於他的位置編號($i \le b_i$)。 - 計算所有區間中,有幾組區間是好區間。 **辦案** 我想這時候我們需要重新詮釋第二點「好區間中,每個數字都要大於等於他的位置編號($i \le b_i$)」,思考這句話跟左右邊界的關聯性,讓我們換句話說「包含數字 $x$ 的區間,在 $x$ 前面最多 $x - 1$ 個數字。(位置編號)」,赫然發現左邊界的極限被決定了! 那麼我們接下來我們可以套用「常見區間關聯」,左邊界動一下,右邊界換一下,區間長度變一下。 已知一個長度為 $k$ 的好區間,如果下一個數字大於等於 $k + 1$,那麼這 $k+1$ 個數字為新的好區間。 | 6 | 3 | 2 | 4 | 區間序列 | |:-----------:| ----- |:-----:|:-----:|:-------- | | | $l_2$ | $r_2$ | | _ 3 2 | | $l_{r_3}$<- | <- | <- | $r_3$ | X X X 4 | | | $l_3$ | | $r_3$ | _ 3 2 4 | 範例:已知 `3 2` 為好區間的區間序列,考慮下一個數字 `4`,因為大於等於 $3 = 2 + 1$,所以 `3 2 4` 為好區間。 已知一個長度為 $k$ 的好區間,如果下一個數字 $j$ 小於 $k + 1$,那麼這 $j$ 個數字為新的好區間。 | 6 | 3 | 2 | 2 | 區間序列 | |:---:|:-----:|:-----------:|:-----:|:----------- | | | $l_2$ | $r_2$ | | _ 3 2 | | | | $l_{r_3}$<- | $r_3$ | _ _ X 2 | | | | $l_3$ | $r_3$ | _ ~~3~~ 2 2 | ## 實作 ```cpp= void solve() { int n; cin >> n; vector<int> as(n); for(int i = 0; i < n; i++) cin >> as[i]; vector<int> l(n, 0); for(int i = 1; i < n; i++) l[i] = max(l[i-1], i - (as[i] - 1)); ll ans = 0; for(int i = 0; i < n; i++) ans += (i - l[i] + 1); cout << ans << '\n'; } ``` **Note** L7: 對於每個數字的左極限座標,且最極限就是在數列的頭,所以初始化為 $0$。 L8: 由於 `as[i] - 1` 為前面最多放幾個數字,所以從 i 往前找座標,這裡不用擔心座標計算出負數,因為我們有初始化最極限為數列的頭,所以可以透過 max 避免掉存取到 `as[-5]` 這種案例。 L11: 為累計每個左極限下的區間數量,得到最終結果。