题意:给定一个序列。要求对一个区间的数进行如下操作:把i~j这段数字全部设为,使得代价最小。每次操作后,数列恢复成原始状态。求所有操作的最小代价和。
首先我们来考虑一下应该如何选取。方便起见,我们就先考虑,先对这个区间排序,我们得到了一个有序序列。
假设存在,那么可以求得代价。
化简得,,
因为代价要小,所以我们要使最大,
我们令,原式就转化成。
那么这一项要尽可能大。因为,所以。
因此随着d的增大而减小。所以w随着d的增大而增大。
同理,时,可以发现w随着d的增大而减小。
因此,,w取到最小值。此时m即为中位数。同理对于每个区间,我们都要取这个区间的中位数。(如果这个序列只有偶数个数,那么取第n/2个,或者第n/2+1个都可以,没有差别的。)
观察到毫无压力。所以我们可以考虑预处理每个区间的中位数,再预处理每个区间的和减去中位数的绝对值的和。回答询问的时候即可。
枚举每一个数,向左扫,记录枚举到时大于等于的个数-比小的个数。向右扫,记录枚举到时大于等于的个数-比小的个数。
如果是的中位数,那么下列两种条件必定满足一种:
1. 是奇数,且
2. 是偶数,且
那么先往左扫,然后用类似Hash的链表存下,然后向右扫的时候查询左边的情况,看看能否满足上述条件的一种。然后存下答案就行。
最后预处理每个区间的和减去中位数的绝对值的和。
综上,预处理复杂度,询问复杂度 。整个算法的时间复杂度就是。
最后贴上代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 1005
int m[maxn*3],pre[maxn],mid[maxn][maxn],a[maxn],id[maxn],n,q;
long long sum[maxn][maxn];
inline int myabs(int x){ return (x<0)?(-x):(x);}
int main()
{
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=n;i++){
memset(m,-1,sizeof(m));
memset(id,0,sizeof(id));
memset(pre,0,sizeof(pre));
int d=0,x=0,tot=1;
for(int j=i;j>=1;j--){
if(a[j]>a[i]) d++; else x++;
id[tot]=j; pre[tot]=m[d-x+maxn]; m[d-x+maxn]=tot++;
}
d=0,x=-1;
for(int j=i;j<=n;j++){
if(a[j]>a[i]) d++; else x++;
for(int p=m[x-d+maxn];p!=-1;p=pre[p]) if(((j-id[p]+1)%2)==0) mid[id[p]][j]=i;
for(int p=m[x-d-1+maxn];p!=-1;p=pre[p]) if(((j-id[p])%2)==0) mid[id[p]][j]=i;
}
}
memset(sum,0,sizeof(sum));
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++) sum[i][j]=sum[i][j-1]+1LL*myabs(a[j]-a[i]);
int x,y;
long long ans=0;
while(q--){
scanf("%d%d",&x,&y);
ans+=sum[mid[x][y]][y]-sum[mid[x][y]][x-1];
}
cout<<ans<<endl;
return 0;
}