[ABC282Ex] Min + Sum

· · 题解

题意

给出序列 a,b,求出满足:

\sum_{i=l}^rb_i+\min_{i=l}^r a_i\le s

(l,r) 数量,其中 1\le l\le r\le n

思路

算是比较套路的题了,区间问题想一下分治总之没有错。

我们对序列分治,然后处理出来分治中点到左右端点的 a 的前缀最小值,b 的前缀和。

然后当前分治区间内的问题就变成了:

\min(mmin_i,mmin_j)+sum_i+sum_j\le s,i\in[l,mid],j\in[mid+1,r]

(i,j) 个数。

其中 mminsum 就表示从分治中点到位置 i 的前缀最小值和前缀和(当然,对于左半区间其实是后缀)。

我们盯一下上面的柿子,发现只需要去掉 \min 就会非常好做。

由于 b_i\ge0 所以 sum 其实是单调的,而 mmin 肯定是单调的,我们枚举 i ,二分出来 mmin_j\ge mmin_ij 的位置,然后对于小于当前位置的 jmmin_j\ge mmin_i 一定成立。所以柿子就是:

mmin_i+sum_i+sum_j\le s,j\in[mid+1,p] $i$ 此时已经确定了,二分一下 $j$ 就行了。 这里追求效率可以将第一次二分改为双指针扫描。 ## code ```cpp typedef long long ll; typedef pair<int,int> pii; typedef vector<int> vi; typedef vector<vector<int> > vii; #define X first #define Y second int mod; const int MAXN=2e5+10; int n;ll s; ll a[MAXN]; int b[MAXN]; ll mmin[MAXN],sum[MAXN]; ll ans=0; void solve(int l,int r) { if(l==r)return ans+=a[l]+b[r]<=s,void(); int mid=(l+r)>>1; solve(l,mid),solve(mid+1,r); mmin[mid+1]=a[mid+1],sum[mid+1]=b[mid+1]; for(int i=mid+2;i<=r;i++)mmin[i]=min(mmin[i-1],a[i]); for(int i=mid+2;i<=r;i++)sum[i]=sum[i-1]+b[i]; mmin[mid]=a[mid],sum[mid]=b[mid]; for(int i=mid-1;i>=l;i--)mmin[i]=min(mmin[i+1],a[i]); for(int i=mid-1;i>=l;i--)sum[i]=sum[i+1]+b[i]; vector<ll>L,R; for(int i=mid+1;i<=r;i++) R.push_back(sum[i]); for(int i=mid;i>=l;i--) L.push_back(sum[i]); int i=l,j=r; while(i<=mid||j>mid) { if(j<=mid||mmin[i]<=mmin[j]&&i<=mid) { int p=upper_bound(R.begin(),R.begin()+j-mid,s-mmin[i]-sum[i])-R.begin(); ans+=p; i++; } else { int p=upper_bound(L.begin(),L.begin()+mid-i+1,s-mmin[j]-sum[j])-L.begin(); ans+=p; j--; } } } signed main() { ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); cin>>n>>s; for(int i=1;i<=n;i++)cin>>a[i]; for(int i=1;i<=n;i++)cin>>b[i]; solve(1,n);cout<<ans<<'\n'; return 0; } ```