题解 P6278 【[USACO20OPEN]Haircut G】
Haircut
题目传送
前言
这题知道算法也知道是维护逆序对的数量,但是自己的思路没能实现出来
然后去看题解,结果没看懂代码为什么这么写(大概是我思维跟不上这么跳跃)
最后思考了半个小时才终于搞明白,下面会较详细的写明原理
(相当于给其他题解写个注释说明吧)
算法
-
树状数组
-
线段树
思路
- 分析题目要求
“对于
嗯,看到这种题目一般会想到 逆序思想 :例如 顺着关闭 = 倒着开放
然后这个不良度其实就是逆序对的数量,而维护逆序对一般首选树状数组
- 样例推广所有
初始长度:5 2 3 3 0
当 j = 0:0 0 0 0 0 逆序对:0
当 j = 1:1 1 1 1 0 逆序对:4
当 j = 2:2 2 2 2 0 逆序对:4
当 j = 3: 3 2 3 3 0 逆序对:5
当 j = 4: 4 2 3 3 0 逆序对:7
我们发现,
再来看,题目中给定
求出这个差距有什么用?
你想啊,第
然后呢?我们用
累加完之后再把当前这个位置加入到树状数组中
- 注意
上面说到,维护逆序对我们一般使用树状数组,而树状数组和线段树是不能维护
代码
- 树状数组:
#include <bits/stdc++.h>
#define lo long long
using namespace std;
lo n,x,ans,a[520010],c[520010],sum[520010];
inline void add(lo i,lo k) {
for(;i<=n+1;i+=i&(-i)) c[i]+=k;
}
inline lo ask(lo i) {
register lo res=0;
for(;i;i-=i&(-i)) res+=c[i];
return res;
}
int main() {
scanf("%lld",&n);
for(register lo i=1;i<=n;i++) {
scanf("%lld",&a[i]);
a[i]++;
}
for(register lo i=1;i<=n;i++) {
x=n-a[i]+1;
sum[a[i]]+=ask(x);
add(x+1,1); //因为x依旧可能为0,所以还是统一加一
}
puts("0");
for(register lo i=1;i<n;i++) {
ans+=sum[i];
printf("%lld\n",ans);
}
return 0;
}
- 线段树:
#include <bits/stdc++.h>
#define lo long long
using namespace std;
lo n,x,ans,a[520010],sum[520010],num[520010];
inline void build(lo l,lo r,lo bh) {
if(l==r) {
sum[bh]=0;
return ;
}
lo mid=(l+r)>>1;
build(l,mid,bh<<1);
build(mid+1,r,bh<<1|1);
sum[bh]=sum[bh<<1]+sum[bh<<1|1];
}
inline void change(lo L,lo l,lo r,lo bh) {
if(l==r) {
sum[bh]++;
return ;
}
lo mid=(l+r)>>1;
if(L<=mid) change(L,l,mid,bh<<1);
else change(L,mid+1,r,bh<<1|1);
sum[bh]=sum[bh<<1]+sum[bh<<1|1];
}
inline lo ask(lo L,lo R,lo l,lo r,lo bh) {
if(L<=l&&r<=R) {
return sum[bh];
}
lo mid=(l+r)>>1,res=0;
if(L<=mid) res+=ask(L,R,l,mid,bh<<1);
if(R>mid) res+=ask(L,R,mid+1,r,bh<<1|1);
return res;
}
int main() {
scanf("%lld",&n);
for(register lo i=1;i<=n;i++) {
scanf("%lld",&a[i]);
a[i]++;
}
build(1,n,1);
for(register lo i=1;i<=n;i++) {
x=n-a[i]+1;
if(x>0) num[a[i]]+=ask(1,x,1,n,1);
change(x+1,1,n,1);
}
puts("0");
for(register lo i=1;i<n;i++) {
ans+=num[i];
printf("%lld\n",ans);
}
return 0;
}
希望讲清楚了点