1. 程式人生 > 其它 >D. Progressions Covering_線段樹維護差分

D. Progressions Covering_線段樹維護差分

線段樹維護差分

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 
小結:

線段樹格外注意邊界