CF#Maximum Average Segment (二分答案 + 字首和 + 貪心)
阿新 • • 發佈:2022-04-01
2022-04-01 11:22:37 星期五
CF#Maximum Average Segment (二分答案 + 字首和 + 貪心)
題目連結
- 題目翻譯
題目的意思是:給定一個有n個元素的陣列和一個數字d。你的任務是找到一個長度不小於d的區間,並且這個區間的算數平均值要儘可能的大。輸出要求打印出兩個數———區間的左端點和右端點。(陣列下標從1開始的!!!)
- 題目分析
要解決的問題是:找到一段長度大於等於d並且算數平均值最大的區間。
- 思路
二分算數平均值,構造一個正確性單調的函式check(),每次將mid傳入函式check()之中,驗證傳入函式的算數平均值符的正確性(所謂正確性單調,例如函式check()在算數平均值的取值範圍內,總會存在一個值x使得之後的正確性不再變化,如正確性為1111111110000000......,之後就一直為0(0代表false,1代表true),在第一個1時的x的值就是答案)。那麼如何構造這個函式呢?
在題目所給的陣列中能不能找到一段區間的算數平均值大於等於傳入函式的算數平均值X
那麼如何快速的求出上式這段區間的和呢?這時候就要用到字首和了!
由於我們要儘可能的讓傳入函式的算數平均值儘可能大,那麼我們就要貪心一下,讓上圖最後一個方程組在每一個r的情況下儘可能滿足。那麼如何才能儘可能的滿足呢?也就是讓P (l - 1)儘可能的小。我們可以拿一個數組m,m[l]記錄區間[0, l - 1]中的最小的P (l - 1),和一個數組pos記錄下取得最小P (l - 1)的位置,把這個位置的數字加一就得到l,也就是左端點,右端點也就是此時的r。
Code
#include <bits/stdc++.h> using namespace std; double a[100010], sum[100010], m[100010], pos[100010];//陣列a記錄陣列的n個數據,陣列sum[i]記錄ai - x的字首和,m[i]記錄區間[0, i - 1]的最小字首和, pos[i]記錄[0, i - 1]取得最小字首和時的位置 int n, d, ansl, ansr; // n為陣列元素個數, d為區間長度,ansl為答案的左端點, ansr為答案的右端點 bool check(double x) { memset(sum, 0, sizeof(sum));//每次二分查詢的時候初始化 memset(m, 0, sizeof(m)); for (int i = 1; i <= n; i++) { sum[i] = sum[i - 1] + a[i] - x; }//預處理字首和 for (int i = 1; i <= n; i++) { if (m[i - 1] > sum[i]) { m[i] = sum[i]; pos[i] = i; } else { m[i] = m[i - 1]; pos[i] = pos[i - 1]; } }//預處理在區間[0,i - 1]時的最小字首和 for (int i = d; i <= n; i++) { if (sum[i] - m[i - d] >= 0) {//滿足第二張圖片推出的最後一個方程組 ansl = pos[i - d] + 1;//左邊界為l而不是l - 1,所以要加1 ansr = i;//右邊界為r return true;//只要找到一段滿足要求的就可以返回了 } } return false;//遍歷了所有r,沒有找到有一段區間滿足條件 } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cin >> n >> d; for (int i = 1; i <= n; i++) { cin >> a[i]; } double l = -1; double r = 1; while (check(r)) { r *= 2; }//試出右邊界 for (int i = 1; i <= 100; i++) { double mid = (l + r) / 2; if (check(mid)) {//check函式的單調性為111111110000000000000.......,因為傳進check函式裡的算數平均值如果越小,那麼肯定是滿足的。 l = mid; } else { r = mid; } } cout << ansl << ' ' << ansr << '\n'; return 0; }