P3157 [CQOI2011]动态逆序对(CDQ分治)
题目描述
对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数。给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数。
输入格式
输入第一行包含两个整数n和m,即初始元素的个数和删除的元素个数。以下n行每行包含一个1到n之间的正整数,即初始排列。以下m行每行一个正整数,依次为每次删除的元素。
输出格式
输出包含m行,依次为删除每个元素之前,逆序对的个数。
输入输出样例
输入 #1
5 4 1 5 3 4 2 5 1 4 2
输出 #1
5 2 2 1 样例解释 (1,5,3,4,2)(1,3,4,2)(3,4,2)(3,2)(3)。
说明/提示
N<=100000 M<=50000
题解: 对于一个逆序对,加一个时间维,越往后删除的时间戳越小,没有删除的值的时间戳为0,这题就变成了一个三维偏序.
我们把时间看作第一维, 从小到大排序, (j,i)作为一个逆序对出现, 则j的时间戳必定小于i的时间戳.
那么有两种情况, Tj < Ti, Pj < Pi, Vj > Vi or Tj < Ti, Pj > Pi, Vj < Vi ( T代表时间,P代表位置,V代表值)
分情况处理即可.
由于我们处理的是每个时间戳所增加的逆序对,所以我们要求一个前缀和,然后倒序输出即可.
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 200010; ll c[maxn]; int n,m; int lowbit(int x){return x & (-x);} void add(int pos,int x){ for(int i = pos;i <= n;i += lowbit(i)){ c[i] += x; } } ll query(int pos){ ll sum = 0; for(int i = pos; i >= 1;i -= lowbit(i)){ sum += c[i]; } return sum; } struct node{ int tim,pos,val; }a[maxn]; int cmp1(node a,node b){//按照时间排序 if(a.tim == b.tim)return a.pos < b.pos; return a.tim < b.tim; } int cmp2(node a,node b){//按照位置从小到大排序 return a.pos < b.pos; } int cmp3(node a,node b){//按照位置从大到小排序 return a.pos > b.pos; } ll pos[maxn],ans[maxn]; void cdq(int l,int r){ if(l == r)return ; int mid = (l + r) / 2; cdq(l,mid),cdq(mid+1,r); int i = mid + 1,j = l;//这里按照第二维 位置从小到大排序,对于右边区间的每一个点i,查询满足 pos(j) < pos(i) && val(j) > val(i)的j有多少个 sort(a + l,a + mid + 1,cmp2); sort(a + mid + 1,a + r + 1,cmp2); for(;i <= r;i++){ while(a[j].pos <= a[i].pos && j <= mid){ add(a[j].val,1); j++; } ans[a[i].tim] += query(n) - query(a[i].val); } for(int i = l;i < j;i++)add(a[i].val,-1);//清空树状数组 i = mid + 1,j = l;//这里按照第二维 位置从大到小排序,对于右边区间的每一个点i,查询满足 pos(j) > pos(i) && val(j) < val(i)的j有多少个 sort(a + l,a + mid + 1,cmp3); sort(a + mid + 1,a + r + 1,cmp3); for(;i <= r;i++){ while(a[j].pos >= a[i].pos && j <= mid){ add(a[j].val,1); j++; } ans[a[i].tim] += query(a[i].val); } for(int i = l;i < j;i++)add(a[i].val,-1);//清空树状数组 } int main() { cin >> n >> m; for(int i = 1;i <= n;i++){ cin >> a[i].val; a[i].tim = 0; pos[a[i].val] = i;//记录每个值的位置 a[i].pos = i; } for(int i = 1;i <= m;i++){ int x; cin >> x; a[pos[x]].tim = m - i + 1;//时间戳更新 } sort(a+1,a+1+n,cmp1); cdq(1,n); for(int i = 1;i <= m;i++)ans[i] += ans[i-1]; for(int i = m;i >= 1;i--)cout << ans[i] << endl; return 0; }