CF460CPresent(二分答案+線段樹)題解
阿新 • • 發佈:2020-11-04
思路
顯然,這題正著不太好推,那麼就考慮二分答案,有一個很大的問題,我們需要在\(O(n\log n)\)或者\(O(n)\)的時間內判斷我們二分答案的可行性。首先肯定想到貪心,但是你會發現每一個元素需要加的值不一樣,加了值以後影響的範圍也不一樣,並不好維護。
因為涉及到區間修改,考慮使用線段樹。我們維護每一位置已經加了多少。
首先明確什麼情況下是必須操作一次的(當前值小於二分所得的最小值的前提下):
- 當前節點已經加的值小於需要加的值
而對於每一次操作,我們可以用線段樹方便地進行區間加。至於每一個點已經加的值,單點查詢就可以了。
程式碼
#include <cstdio> #include <algorithm> #include <cstring> using namespace std; const int maxn = 1e5 + 10; int n,m,w,a[maxn]; struct Seg_Tree{ #define lc(x) x << 1 #define rc(x) x << 1 | 1 int c[maxn << 2],tag[maxn << 2]; void clear(){ memset(c,0,sizeof(c)); memset(tag,0,sizeof(tag)); } void f(int l, int r, int p, int x){ c[p] += (r - l + 1) * x; tag[p] += x; } void downdate(int l, int r, int p){ if(tag[p]){ int mid = (l + r) >> 1; f(l, mid, lc(p), tag[p]); f(mid + 1, r, rc(p), tag[p]); tag[p] = 0; } } void add(int L, int R, int l, int r, int p, int x){ if(L <= l && R >= r){ f(l, r, p, x); return; } downdate(l, r, p); int mid = (l + r) >> 1; if(mid >= L) add(L, R, l, mid, lc(p), x); if(mid < R) add(L, R, mid + 1, r, rc(p), x); c[p] = c[lc(p)] + c[rc(p)]; } int query(int l, int r, int p, int pos){ if(l == r) return c[p]; downdate(l, r, p); int mid = (l + r) >> 1; if(mid >= pos) return query(l, mid, lc(p), pos); else return query(mid + 1, r, rc(p), pos); } }tree; inline int check(int mid){ int cnt = 0, flag = 1; tree.clear(); //記得清空 for(int i = 1; i <= n; ++ i){ if(a[i] < mid){ int val = tree.query(1, n, 1, i); if(val < mid - a[i]){ cnt += mid - a[i] - val; tree.add(i, i + w - 1, 1, n, 1, mid - a[i] - val); } } if(cnt > m){flag = 0; break;} } if(cnt > m || !flag) return 0; else return 1; } int main(){ int h = 0x3f3f3f3f, l = 0x3f3f3f3f, mid; //這裡h不能設成1e9+10,因為答案可能會大於它 scanf("%d%d%d", &n, &m, &w); for(int i = 1; i <= n; ++ i){ scanf("%d", a + i); l = min(l, a[i]); } while(h >= l){ mid = (h + l) >> 1; if(!check(mid)) h = mid - 1; else l = mid + 1; } printf("%d\n", l - 1); return 0; }