D. Progressions Covering_線段樹維護差分
阿新 • • 發佈:2022-04-14
線段樹維護差分
D. Progressions Covering
題目大意:
數列a原來全是0,可以無限次進行一種操作,每次操作可以選擇一段長度為k的區間,對該區間的數字分別對應加上1,2,3,...,k。再給出數列b,問最少操作幾次可以使得a數列的每一個數字不小於b數列中的對應數字。
思路和程式碼:
可以操作題目給出的陣列,將其中每一個數減到小於等於0。(當然也可以操作0陣列,然後把每個數加到對應大於等於,都可以)
實話說,看到等差數列就會去想維護一個差分線段樹。
每個數要減到小於等於0。對於第i個數ai,要減到小於等於0,最賺的就是把它放在區間的儘可能的右端。所以我們從後往前減數即可。
有了以上思路暴力去做複雜度就是O(nk)。遍歷每個數的O(n)是不能優化的,那麼考慮去優化這個O(k),即區間等差數列加減法。將等差數列加減法放到差分數列上去做,即可轉化為區間加減法
當然本題要注意當i走到了區間長k的前面,我們就不能把當前i位置放到區間最右邊,不然回超出區間。所以前面說的是儘可能的右端。
ll n , m , k ; ll a[N] ; struct Node{ int l , r ; ll sum , lzy ; }tr[N << 2]; void pushup(Node &f , Node &l , Node &r){ f.sum = l.sum + r.sum ; } void update(Node &u , ll det){ u.sum += 1LL * (u.r - u.l + 1) * det ; u.lzy += 1LL * det ; } void pushdown(Node &f , Node &l , Node &r){ update(l , f.lzy) ; update(r , f.lzy) ; f.lzy = 0 ; } ll query(int now , int l , int r){ // 差分陣列單點查詢 // cout << tr[now].l << "_" << tr[now].r << "\n" ; if(l <= tr[now].l && tr[now].r <= r) return tr[now].sum ; pushdown(tr[now] , tr[now << 1] , tr[now << 1 | 1]) ; int mid = tr[now].l + tr[now].r >> 1 ; ll res = 0 , lft = 0 , rit = 0 ; if(l <= mid) lft = query(now << 1 , l , r) ; if(mid < r ) rit = query(now << 1 | 1 , l , r) ; res = lft + rit ; return res ; } void modify(int now , int l , int r , ll det){ if(l <= tr[now].l && tr[now].r <= r){ update(tr[now] , det) ; return ; } pushdown(tr[now] , tr[now << 1] , tr[now << 1 | 1]) ; int mid = tr[now].l + tr[now].r >> 1 ; if(l <= mid) modify(now << 1 , l , r , det) ; if(mid < r ) modify(now << 1 | 1 , l , r , det) ; pushup(tr[now] , tr[now << 1] , tr[now << 1 | 1]) ; } void build(int now , int l , int r){ //cout << l << " " << r << "\n" ; if(l == r){ tr[now] = {l , r , a[l] - a[l - 1] , 0} ; return ; } tr[now] = {l , r , 0 , 0} ; int mid = l + r >> 1 ; build(now << 1 , l , mid) ; build(now << 1 | 1 , mid + 1 , r) ; pushup(tr[now] , tr[now << 1] , tr[now << 1 | 1]) ; } void solve(){ cin >> n >> k ; rep(i , 1 , n) cin >> a[i] ; build(1 , 1 , n) ; ll ans = 0 ; for(int i = n ; i >= 1 ; i -- ){ ll ai = query(1 , 1 , i) ; if(ai > 0LL){ ll L = min(1LL * i , 1LL * k) ; ll d = (ai + L - 1) / L ; ans += d ; modify(1 , i - L + 1 , i , -1LL * d) ; if(i + 1 <= n)modify(1 , i + 1 , i + 1 , -1LL * L * d) ; } } cout << ans << "\n" ; }//code_by_tyrii
小結:
線段樹格外注意邊界