K-xay loves sequence
首先不考虑模 k k k的限制,容易想到对原数组做一个差分得到 d i = a i − a i − 1 d_i=a_i-a_{i-1} di=ai−ai−1,显然对于 ∀ 1 ≤ i ≤ n a i = 0 \forall_{1\leq i\leq n} a_i=0 ∀1≤i≤nai=0 等价于 ∀ 1 ≤ i ≤ n d i = 0 \forall_{1\leq i\leq n} d_i=0 ∀1≤i≤ndi=0,而对于原数组的区间 ± 1 \pm1 ±1等价于在差分数组 d d d中找到两个数一个数+1另一个数-1,或者说找到一个位置 pos \text {pos} pos单独+1或者-1(这意味着原数组 [ a pos → a n ] ± 1 [a_{\text{pos}}\to a_n] \pm1 [apos→an]±1)。上面的最优解显然是 max { ∑ d + , ∣ ∑ d − ∣ } \max\{\sum d_+,|\sum{d_-}|\} max{∑d+,∣∑d−∣}
上述变换有些麻烦,如果我们引入 a n + 1 = 0 a_{n+1}=0 an+1=0,并且引入 d n + 1 = a n + 1 − a n d_{n+1}=a_{n+1}-a_n dn+1=an+1−an后,对于原数组进行区间 ± 1 \pm1 ±1等价于在差分数组 d 1 → n + 1 d_{1\to n+1} d1→n+1选两个数一个+1另一个-1。并且不难发现无论怎么进行上述操作总有 ∑ i = 1 n + 1 d i = 0 \sum_{i=1}^{n+1} d_i=0 ∑i=1n+1di=0最优解 max { ∑ d + , ∣ ∑ d − ∣ } = ∑ i = 1 n + 1 ∣ d i ∣ 2 \max\{\sum d_+,|\sum{d_-}|\}=\frac{\sum_{i=1}^{n+1} |d_i|}{2} max{∑d+,∣∑d−∣}=2∑i=1n+1∣di∣
考虑模 k k k的意义下意味着我们可以花费0的代价使得某些 a i ± k a_i\pm k ai±k,对应在差分数组上即是 d i ± k , d i + 1 ∓ k d_i\pm k,d_{i+1}\mp k di±k,di+1∓k,换句话说现在可以让成对的 d i ± k , d j ∓ k d_i\pm k,d_j\mp k di±k,dj∓k最终结果仍然是让 ∀ 1 ≤ i ≤ n d i + 1 = 0 \forall_{1\leq i\leq n} d_{i+1}=0 ∀1≤i≤ndi+1=0。
不难发现 d i = d i + k d_i=d_i+k di=di+k那么一定有 d i < 0 d_i<0 di<0,同理 d i = d i − k d_i=d_i-k di=di−k那么一定有 d i ≥ 0 d_i\ge0 di≥0
如果 d i = d i + k d_i=d_i+k di=di+k,那么贡献从 ∣ d i ∣ 2 → ∣ d i + k ∣ 2 , Δ = k − 2 ∣ d i ∣ 2 \frac{|d_i|}{2}\to\frac{|d_i+k|}{2},\Delta=\frac{k-2|d_i|}{2} 2∣di∣→2∣di+k∣,Δ=2k−2∣di∣
如果 d i = d i − k d_i=d_i-k di=di−k,那么贡献从 ∣ d i ∣ 2 → ∣ d i − k ∣ 2 , Δ = k − 2 ∣ d i ∣ 2 \frac{|d_i|}{2}\to\frac{|d_i-k|}{2},\Delta=\frac{k-2|d_i|}{2} 2∣di∣→2∣di−k∣,Δ=2k−2∣di∣
于是只需要把 d i d_i di分为小于0的一组和大于0的一组,按照 Δ = k − 2 ∣ d i ∣ 2 \Delta=\frac{k-2|d_i|}{2} Δ=2k−2∣di∣从小到大排序,每次取两数组中开头的两个 ( Δ + ) + ( Δ − ) (\Delta_+)+(\Delta_-) (Δ+)+(Δ−),如果能使答案变小即 ( Δ + ) + ( Δ − ) < 0 (\Delta_+)+(\Delta_-)<0 (Δ+)+(Δ−)<0即一直取。
首先如果没有区间 l , r l,r l,r的限制,每次询问只给一个 k k k的话可以二分取了多少对 d − + k , d + − k d_{-}+k,d_{+}-k d−+k,d+−k,快速的求出答案。显然加上区间限制只需要套一个主席树即可。
注意对于 [ l , r ] [l,r] [l,r]区间来说差分数组是 ( a l − 0 ) , d l + 1 , … d r , ( 0 − a r ) (a_l-0),d_{l+1},\dots d_{r},(0-a_r) (al−0),dl+1,…dr,(0−ar),也就是主席树维护 d i d_i di,然后每次询问会增添两个数 ( a l − 0 ) (a_l-0) (al−0)和 ( 0 − a r ) (0-a_r) (0−ar),一个插在正主席树,一个在负主席树。一个显然的想法是单点修改2次,然后询问过后删除,就如下面注释的代码。但是主席树不支持修改,因为修改不仅仅影响一棵主席树,而是会影响许多棵树,于是只需要在询问的时候带上这个数即可。
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
template <class T=int> T rd()
{
T res=0;T fg=1;
char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') fg=-1;ch=getchar();}
while( isdigit(ch)) res=(res<<1)+(res<<3)+(ch^48),ch=getchar();
return res*fg;
}
const int N=200010;
const int U=(1ll<<31)-1;
int a[N],d[N];
ll s[N];
int n,m;
struct node
{
int l,r;
ll v,ct;
}tree[N*100];
int rt[2][N],cnt;
void update(int l,int r,int pre,int &u,int v,int c)
{
u=++cnt;
tree[u]=tree[pre];
tree[u].ct+=c;
tree[u].v+=v*c;
if(l==r) return;
int mid=l+r>>1;
if(v<=mid)
update(l,mid,tree[pre].l,tree[u].l,v,c);
else
update(mid+1,r,tree[pre].r,tree[u].r,v,c);
}
// void change(int &u,int l,int r,int v,int c)
// {
// if(!u) u=++cnt;
// tree[u].ct+=c;
// tree[u].v+=v*c;
// if(l==r) return;
// int mid=l+r>>1;
// if(v<=mid)
// change(tree[u].l,l,mid,v,c);
// else
// change(tree[u].r,mid+1,r,v,c);
// }
// // 求前k大的sum
// ll query(int l,int r,int L,int R,int k)
// {
// if(!k) return 0ll;
// if(l==r) return 1ll*k*l;
// int mid=l+r>>1;
// int tmp=tree[tree[R].r].ct-tree[tree[L].r].ct;
// if(k<=tmp)
// return query(mid+1,r,tree[L].r,tree[R].r,k);
// else
// return (1ll*tree[tree[R].r].v-tree[tree[L].r].v)+query(l,mid,tree[L].l,tree[R].l,k-tmp);
// }
// // 计算数组l~r的答案 取前k大
// ll calc(int l,int r,int k,int x)
// {
// // a[l]+d[l+1]~d[r]+abs(0-a[r])
// ll v=(s[r]-s[l]+a[r]+a[l])/2;
// ll s1=query(0,U,rt[0][l],rt[0][r],k);
// ll s2=query(0,U,rt[1][l],rt[1][r],k);
// return v-s1-s2+1ll*k*x;
// }
// 求前k大的sum
ll query(int l,int r,int L,int R,int k,int v)
{
if(!k) return 0ll;
if(l==r) return 1ll*k*l;
int mid=l+r>>1;
int tmp=tree[tree[R].r].ct-tree[tree[L].r].ct+(mid<v&&v<=r);
if(k<=tmp)
return query(mid+1,r,tree[L].r,tree[R].r,k,v);
else
return (1ll*tree[tree[R].r].v-tree[tree[L].r].v)+(mid<v&&v<=r)*v+query(l,mid,tree[L].l,tree[R].l,k-tmp,v);
}
// 计算数组l~r的答案 取前k大
ll calc(int l,int r,int k,int x)
{
// a[l]+d[l+1]~d[r]+abs(0-a[r])
ll v=(s[r]-s[l]+a[r]+a[l])/2;
ll s1=query(0,U,rt[0][l],rt[0][r],k,a[l]);
ll s2=query(0,U,rt[1][l],rt[1][r],k,a[r]);
return v-s1-s2+1ll*k*x;
}
int main()
{
n=rd(),m=rd();
for(int i=1;i<=n;i++)
{
a[i]=rd();
d[i]=abs(a[i]-a[i-1]);
s[i]=s[i-1]+d[i];
if(a[i]>=a[i-1])
{
update(0,U,rt[0][i-1],rt[0][i],d[i],1);
rt[1][i]=rt[1][i-1];
}
else
{
update(0,U,rt[1][i-1],rt[1][i],d[i],1);
rt[0][i]=rt[0][i-1];
}
}
while(m--)
{
int x=rd(),y=rd(),k=rd();
// a[x] d[x+1] d[x+2]... d[y] abs(0-a[y])
// change(rt[0][y],0,U,a[x],+1);
// change(rt[1][y],0,U,a[y],+1);
int l=0,r=min(tree[rt[0][y]].ct-tree[rt[0][x]].ct,tree[rt[1][y]].ct-tree[rt[1][x]].ct)+1;
//cout<<l<<' '<<r<<'\n';
while(l<r)
{
int mid=l+r>>1;
if(calc(x,y,mid,k)<=calc(x,y,mid+1,k)) r=mid;
else l=mid+1;
}
printf("%lld\n",calc(x,y,l,k));
// change(rt[0][y],0,U,a[x],-1);
// change(rt[1][y],0,U,a[y],-1);
}
return 0;
}
这篇博客探讨了如何利用差分数组解决区间修改问题,特别是在模数限制下的优化策略。通过引入辅助变量和差分,将原数组的区间±1操作转化为对差分数组的选择性修改,以最小化某个目标函数。文章详细介绍了如何使用主席树进行区间查询和修改,以及在询问过程中如何选取合适的修改策略来提高效率。最后,通过一个具体的代码实现展示了这种思想的应用。
344

被折叠的 条评论
为什么被折叠?



