题解 P2717 【寒假作业】
WanderingTrader · · 题解
本题有多种方法,此处介绍树状数组与cdq分治两种。
题目分析
看到平均值无脑所有
于是题目转化为求出满足
把
因为
解法一 树状数组
类似逆序对的树状数组解法。维护一个数列
注意由于全部减去了
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn=1e5+5;
int a[maxn],id[maxn],rk[maxn],bit[maxn],n,k;
LL ans;
bool cmpID(int u,int v){return a[u]<a[v]||(a[u]==a[v]&&u<v);}
int LSB(int x){return x&-x;}
void add(int x){for(;x<=n+1;x+=LSB(x)) ++bit[x];}
int query(int x)
{
int sum=0;
for(;x;x-=LSB(x)) sum+=bit[x];
return sum;
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;++i) scanf("%d",a+i);
for(int i=1;i<=n;++i) {a[i]+=a[i-1]-k; id[i]=i;}
sort(id,id+n+1,cmpID);
for(int i=0;i<=n;++i) rk[id[i]]=i+1;
for(int i=0;i<=n;++i)
{
ans+=query(rk[i]);
add(rk[i]);
}
printf("%lld\n",ans);
return 0;
}
本人的离散化写法比较奇怪(为了省码量没有使用结构体),勿喷。
rk 数组表示
此外,由于此题中 rk 数组出现了
解法二 cdq分治
首先我们回忆一下用归什么排序解决逆序对问题的简要步骤:
- 将原序列划分为
[l,mid] 和(mid,r] 两部分。 - 递归求解
[l,mid] 和(mid,r] 两个区间内部的逆序对,并将它们进行排序。 - 在合并序列的同时计算跨两个区间的逆序对。
本题其实是一样的 众所周知cdq分治本质上就是归什么排序的扩展延伸
- 将原序列划分为
[l,mid] 和(mid,r] 两部分。 - 递归求解
[l,mid] 和(mid,r] 两个区间内部的偏序,并将它们进行排序。 - 在合并序列的同时计算跨两个区间的偏序。
和归什么排序一样,定义
如果
if(l==r) return;
int mid=(l+r)>>1,i=l,j=mid+1,k=l;
cdq(l,mid); cdq(mid+1,r);
维护双指针
对每个
在右移的过程中将
那么对这个
for(j=mid+1;j<=r;)
{
while(i<=mid&&a[i]<=a[j]) t[k++]=a[i++];
t[k++]=a[j++];
ans+=i-l;
}
操作结束后,右半部分已经进入临时序列,但左半部分可能并没有,所以一定要记得收个尾。然后将临时数组拷贝回
for(;i<=mid;) t[k++]=a[i++];
for(i=l;i<=r;++i) a[i]=t[i];
cdq函数如下:
void cdq(int l,int r)
{
if(l==r) return;
int mid=(l+r)>>1,i=l,j=mid+1,k=l;
cdq(l,mid); cdq(mid+1,r);
for(j=mid+1;j<=r;)
{
while(i<=mid&&a[i]<=a[j]) t[k++]=a[i++];
t[k++]=a[j++];
ans+=i-l;
}
for(;i<=mid;) t[k++]=a[i++];
for(i=l;i<=r;++i) a[i]=t[i];
}
主函数调用
后记
上面一行为树状数组的提交记录,下面一行为cdq分治的提交记录。
可以看到cdq完美碾压树状数组。
这不难理解。树状数组因为要离散化,时间差不多有两个 LSB,add,query)也导致码量不可避免的变长。
但是,树状数组最大的优点就是:好想,好写,不易写挂。我一开始写的cdq分治犯了几个sb错误,结果过了样例但爆了零。
所以能用树状数组还是尽量用吧。