Always keep a beg|

luckydrawbox

园龄:4个月粉丝:1关注:2

CF1661D Progressions Covering

Link\text{Link}

题意

有一个 nn 个数的整数序列 b1nb_{1\sim n},每次可以选择一个长度为 kk连续子区间从左到右分别减去 1,2,3,,k1,k1,2,3,…,k-1,k,求最少操作多少次可以使所有的 bi0b_i\le0

分析

我们先抛开如何求解,先想想怎样快速执行一次操作。

执行那么诡异的操作的数据结构肯定是没有的,不过如果我们求出 bb 数组的差分数组的相反数数组 ai=bi1bia_i=b_{i-1}-b_{i},那么一次操作 [l,r][l,r] 就相当于把 alra_{l\sim r}11,再把 ar+1a_{r+1} 减去 kk,且 bi=j=1iajb_i=-\sum_{j=1}^ia_j,转化成了我们常见的形式,我这里使用了一种直观而通用的数据结构——树状数组分块

接下来尝试求解,第一种容易想到的思路是我们从 1n1\sim n 分别考虑每个 bib_i,在之前做过的操作的基础上执行 bib_i 次操作 [i,i+k1][i,i+k-1] 使 bi0b_i\le0,并对后面的数产生影响,边界情况要特殊处理。

不过这个做法过不了最后一个样例,显然到 bib_i 时每次操作只减少 11 是一个极大的浪费,为了尽可能节省次数,我们可以倒序执行操作,在前面的基础上,对于每个 bib_i,执行 bik\left\lceil\frac{b_i}{k}\right\rceil[ik+1,i][i-k+1,i],边界情况仍要特殊处理。

倒序处理还有一个好处,就是当处理 bib_i 时,bi+1nb_{i+1\sim n} 已经处理完了,因此将 ai+1a_{i+1} 减去 kk 这个过程可有可无,我们可以偷懒省略掉。

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
long long read(){
	long long x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
void write(long long x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
const int N=3e5+10;
int n,k;
ll b[N],a[N],ad[N],sum[N],len,ans;
void add(int l,int r,ll v){//分块的区间修改 
	int kl=l/len,kr=r/len;
	if(kl==kr){
		for(int i=l;i<=r;i++){
			a[i]+=v;
			sum[kl]+=v;
		}
		return;
	}
	for(int i=l;i<=kl*len+len-1;i++){
		a[i]+=v;
		sum[kl]+=v;
	}
	for(int i=kl+1;i<kr;i++){
		ad[i]+=v;
		sum[i]+=len*v;
	}
	for(int i=kr*len;i<=r;i++){
		a[i]+=v;
		sum[kr]+=v;
	}
}
ll ask(int l,int r){//分块的区间求和 
	ll ans=0;
	int kl=l/len,kr=r/len;
	if(kl==kr){
		for(int i=l;i<=r;i++)
			ans+=a[i]+ad[kl];
		return ans;
	}
	for(int i=l;i<=kl*len+len-1;i++)
		ans+=a[i]+ad[kl];
	for(int i=kl+1;i<kr;i++)
		ans+=sum[i];
	for(int i=kr*len;i<=r;i++)
		ans+=a[i]+ad[kr];
	return ans;
}
int main(){
	n=read();k=read();
	len=sqrt(n);
	for(int i=1;i<=n;i++){
		b[i]=read();
		add(i,i,0-b[i]+b[i-1]);
	}
	for(int i=n;i>=1;i--){
		ll v=ask(1,i);//v就是b[i] 
		if(v>=0)//已经满足要求就不用计算 
			continue;
		v=-v;//把负数改为正数便于计算 
		if(i>=k){
			add(i-k+1,i,(v/k)+(v%k>0));//(v/k)+(v%k>0)相当于v/k向上取整
			ans+=(v/k)+(v%k>0);
		}
		else{//边界情况 
			add(1,k,(v/i)+(v%i>0)); 
			ans+=(v/i)+(v%i>0);
		}
	}
	write(ans);
	return 0;
}

本文作者:luckydrawbox

本文链接:https://www.cnblogs.com/luckydrawbox/p/18526530

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   luckydrawbox  阅读(3)  评论(0编辑  收藏  举报  
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起