题目大意:对于序列A,它的逆序对数定义为满足i
分析:这题是树状数组套主席树水题,按顺序加入数字,求出最后的逆序对数,删除数字时只需要删除该数字对答案的贡献,该数字的贡献有两种:位置在它前面值大于它 和 位置在它后面值小于它。用主席树这个东西很好求,其中后一种用区间减法可以求得。删除掉该数字后更新一下主席树即可。
主席树题解:https://blog.csdn.net/qq_41997978/article/details/89442123
cdq分治解法:初学cdq分治,我们也可以用cdq分治在离线搞掉这题,常数小,时间和空间都优于树套树。
cdq分治的基本思想:cdq分治主要用来解决偏向类问题,也可以解决查询-修改操作的数据结构问题,当问题能转化为偏序类问题,且支持离线操作时可以使用该算法,网上介绍cdq分治的博客非常多。
cdq分治的基本思想在于:先递归求解左子区间和右子区间的问题,再求出左区间操作对右区间答案的贡献,过程类似于归并排序,可以回忆一下归并排序求逆序对的过程。cdq分治在应对不同情况时有不同的写法,这里蒟蒻还没完全掌握。。。暂时不讨论,后期再补上。
不懂cdq分治的同学可以先学一下cdq分治。
具体做法:原问题删除数字可以转化为插入数字,这样第一个删除的数字变成最后一个插入的数字, 每个插入数字的操作都有一个时间。操作结构体有时间,位置,值三个属性,分别为t,pos,val。问题可以转化为求出所有 ti < t, p < pos , v > val 的有序对,答案可以由每一个操作单独的贡献求和得到,而每一个操作单独的贡献显然就是一个三维偏向问题:求出所有 ti < t, p < pos , v > val 个数。
事实上逆序对还有有两种,另一种是ti < t,p > pos,v < val,但仔细一想似乎在普通的逆序对问题只考虑了上面一种情况?这是因为我们可以按插入顺序依次求解每个刚插入的数值得到的贡献,最后求和,因为此时它后面肯定还没有插入数字,可以不重复的求出所有答案。
但这里将删除操作改成了插入操作,情况就变了。
如果没有将删除改成插入,原序列插入时间ti < t的话,插入位置p 一定 < pos (想一想,为什么)
但这里因为我们将删除变成了插入,导致一些数字的操作时间改变,而它们的位置却不变。
如果还按只考虑一种情况的方法来求逆序对,显然会漏解,因为有的数字插入后,它的后面是存在数字的,换句话说不再是完全按位置顺序依次插入了。
因此求解答案要考虑两种情况,操作先按插入时间排序,cdq分治按pos进行排序,用权值树状数组在log时间内求出v < val的值。在求出一种情况后,要将树状数组清除,再求第二种情况。最后还要将树状数组清除。
虽然看起来是每个数字都计算两种逆序对,但只有操作改变的那些数字才会有两种贡献(想一想,为什么)
看代码可以好好理解一下。
#include
using namespace std;
#define lowbit(i) (i & (-i))
const int maxn = 5e5 + 10;
int n,m;
int top;
struct node {
long long t,pos,val;
bool operator < (const node & rhs) const {
if(pos == rhs.pos) return val < rhs.val;
return pos < rhs.pos;
}
}q[maxn * 3],tmp[maxn];
long long mp[maxn],a[maxn],ans[maxn];
long long sum[maxn];
long long getsum(int p) {
long long ans = 0;
for(int i = p; i; i -= lowbit(i)) ans += sum[i];
return ans;
}
void modify(int p,int v) {
for(int i = p; i <= n; i += lowbit(i)) sum[i] += v;
}
bool cmp(node a,node b) {
return a.t < b.t;
}
void cdq(int l,int r) {
if(l == r) return;
int mid = l + r >> 1;
cdq(l,mid);cdq(mid + 1,r);
top = 0;
for(int i = l,j = mid + 1; i <= mid || j <= r;) {
if(i <= mid && (j > r || q[i] < q[j])) {
modify(q[i].val,1);
tmp[++top] = q[i++];
}
else {
ans[q[j].t] += getsum(n) - getsum(q[j].val);
tmp[++top] = q[j++];
}
}
for(int i = l; i <= mid; i++) {
modify(q[i].val,-1);
}
for(int i = l; i <= r; i++) {
q[i] = tmp[i - l + 1];
}
for(int i = r; i >= l; i--) {
if(q[i].t <= mid) modify(q[i].val,1);
else ans[q[i].t] += getsum(q[i].val);
}
for(int i = r; i >= l; i--)
if(q[i].t <= mid) modify(q[i].val,-1);
}
int main() {
scanf("%d%d",&n,&m);
int x,t = n;
for(int i = 1; i <= n; i++) {
scanf("%lld",&a[i]);
q[i] = (node) {0,i,a[i]};
mp[a[i]] = i;
}
for(int i = 1; i <= m; i++) {
scanf("%d",&x);
q[mp[x]].t = t--;
}
for(int i = 1; i <= n; i++) {
if(!q[i].t) q[i].t = t--;
}
sort(q + 1,q + n + 1,cmp);
cdq(1,n);
for(int i = 1; i <= n; i++) ans[i] += ans[i - 1];
for(int i = n; i > n - m; i--) {
printf("%lld\n",ans[i]);
}
return 0;
}