题意:
每次可以选择一个子区间使其+1或-1后对k取模,问把区间内所有数变为0的最小操作次数。
思路:
感觉碰到过很多次,最近的沈阳 J 题,还有西安邀请赛的B也是类似的题。
如果不考虑取模,那么求出差分数组 d,问题转换为每次操作对d数组的一个数+1,一个数-1。那么肯定是对正数+1,负数-1,那么答案就是所有数的绝对值之和除2.
考虑取模,那么相当于对每个数 + k或者 - k,要使贡献减小,必然是对负数 + k ,整数 - k 。此时每个数的贡献变化量为
k
−
2
∗
∣
d
[
i
]
∣
k - 2 * |d[i]|
k−2∗∣d[i]∣,所以我们把整数分为一组,负数分为一组,从大到小排序,每次分别从其中选取最大值,知道贡献不能再减小。对于排序,我们可以用主席树优化,取前k大的和,三分取极小值即可。
具体分析参考以下博客:添加链接描述
代码:
#include <bits/stdc++.h>
#define GO ios::sync_with_stdio(0);cin.tie(0),cout.tie(0)
using namespace std;
typedef long long ll;
const int maxn = 6e5 + 7;
const ll inf = (1ll << 30);
const int mod = 998244353;
int T;
int n,m,a[maxn],d[maxn];
ll pre[maxn];
ll ls[maxn * 20],rs[maxn * 20] , sum[maxn * 20] , sz[maxn * 20],rt[2][maxn];
int id;
int update(int k, int l , int r, int root){
int res = ++id;
ls[res] = ls[root],rs[res] = rs[root],sz[res] = sz[root] + 1,sum[res] = sum[root] + k;
if(l == r) return res;
int mid = (l + r) / 2;
if(k <= mid) ls[res] = update(k , l , mid , ls[root]);
if(k > mid) rs[res] = update(k , mid + 1 , r , rs[root]);
return res;
}
ll query(int l,int r,int root1,int root2,int k,int val){ //前k大和
if(l == r){
return 1ll * l * k; // 注意这里是l * k ,当前还需要选 k 个数 , 只能选 l .不能写 sum[root1] - sum[root2],可能选的大于k 个。
}
int mid = (l + r) / 2;
int x = sz[rs[root1]] - sz[rs[root2]] + (mid + 1 <= val && val <= r); //判断右区间的值是否大于k个,还要带上 val ,边界。
ll res = sum[rs[root1]] - sum[rs[root2]] + 1ll * (mid + 1 <= val && val <= r) * val;
if(x >= k) return query(mid + 1 , r ,rs[root1],rs[root2],k , val);
return query(l, mid , ls[root1],ls[root2],k - x , val) + res;
}
ll cal(int l , int r , int cnt , int k){ //选前k大的值
ll sum = (pre[r] - pre[l] + a[l] + a[r]) / 2;
ll res1 = query(0 , inf , rt[0][r] , rt[0][l] , k , a[l]);
ll res2 = query(0 , inf , rt[1][r] , rt[1][l] , k , a[r]);
return sum + 1ll * cnt * k - res1 - res2;
}
int main (){
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; i ++){
scanf("%d",&a[i]);
d[i] = a[i] - a[i - 1];
pre[i] = pre[i - 1] + abs(d[i]);
}
for(int i = 1; i <= n; i ++){
if(d[i] > 0){
rt[0][i] = update(d[i], 0 , inf , rt[0][i - 1]);
rt[1][i] = rt[1][i-1];
}
else{
rt[1][i] = update(-d[i], 0 , inf , rt[1][i - 1]);
rt[0][i] = rt[0][i-1];
}
}
while(m--){
int x,y,k;
scanf("%d%d%d",&x,&y,&k);
int l = 0 , r = min(sz[rt[0][y]] - sz[rt[0][x]] , sz[rt[1][y]] - sz[rt[1][x]]) + 2 , mid;
ll ans = 1e18;
while(l <= r){
mid = (l + r) / 2;
ll res1 = cal(x , y , k , mid) , res2 = cal(x , y , k , mid + 1);
if(res1 > res2){
l = mid + 1;
ans = min(ans , res2);
}
else{
r = mid - 1;
ans = min(ans , res1);
}
}
printf ("%lld\n",ans);
}
}