在发表文章之前,蒟蒻只会用归并排序求逆序对.在考试中遇到了求数列中逆序对与本质不同的逆序对,立刻露出爆零本质,于是学习了一下树状数组求这两个子问题.
1.逆序对
定义:在数列中ai>aj&&i<j,将这两个数称作逆序对.
暴力思想:直接找这个数前面比它大的数,复杂度O(n^2)???;
树状数组:其实就是暴力的优化,在O(n)遍历是将遍历到的点都"染色","染色"后立马查询比它小的数染色的总数,用这个数的id-sum就是它前面比它大的数,即逆序对,O(nlogn).离散后再做可针对数比较大的情况.
ps:也可直接查它前面比它大的数,即查区间(nownum,maxnum)当前被染色个数.
2.本质不同的逆序对
其实就是将每个数第一次出现才染色,最后一次出现才统计逆序对.
ps:对于每个数,最后一次数之前出现的该数的逆序对的是最后一次数统计逆序对的子集,
第一次之后的影响都是第一次影响的重复累加.
#include<iostream> #include<algorithm> #include<cstdio> #include<cmath> using namespace std; #define R register #define e exit(0) #define ll long long const int maxn=5*1e5+10; long long n,ans1,ans2,jz=-1,a[maxn],tree[maxn],fir[maxn],endd[maxn],q[maxn]; struct shu{ ll v,id; }num[maxn]; bool cmp(shu x,shu y) { if(x.v==y.v) return x.id<y.id;//注意离散判重. return x.v<y.v; } inline long long fd() { long long s=1,t=0; char c=getchar(); while(c<'0'||c>'9') { if(c=='-') s=-1; c=getchar(); } while(c>='0'&&c<='9') { t=t*10+c-'0'; c=getchar(); } return s*t; } long long lowbit(long long x) { return x&(-x); } void add(ll x,ll v) { while(x<=n) { tree[x]+=v; x+=lowbit(x); } } void add1(ll x,ll v) { while(x<=jz) { tree[x]+=v; x+=lowbit(x); } } long long ask(ll x) { long long sum=0; while(x) { sum+=tree[x]; x-=lowbit(x); } return sum; } int main() { freopen("deseq.in","r",stdin); freopen("deseq.out","w",stdout); n=fd(); for(R ll i=1;i<=n;++i) { num[i].v=fd(),num[i].id=i; if(!fir[num[i].v]) fir[num[i].v]=i; endd[num[i].v]=i; jz=max(jz,num[i].v); q[i]=num[i].v; } sort(num+1,num+1+n,cmp); for(R ll i=1;i<=n;++i) a[num[i].id]=i; for(R ll i=1;i<=n;++i) { add(a[i],1); ans1+=i-ask(a[i]); } for(R ll i=1;i<=n;++i) tree[i]=0; for(R ll i=1;i<=n;++i) { if(fir[q[i]]==i) add1(q[i],1); if(endd[q[i]]==i) ans2+=ask(jz)-ask(q[i]);//其实也可以离散,但要对同数值的数处理. } printf("%lld %lld",ans1,ans2); return 0; }