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
小结:
线段树格外注意边界
标签:数列,Covering,ai,ll,差分,区间,Progressions,线段 来源: https://www.cnblogs.com/tyriis/p/16146259.html