P3157 [CQOI2011]动态逆序对

本文介绍了一道经典算法题“CQOI2011动态逆序对”的解决方案,通过使用cdq分治技巧,解决在序列中动态删除元素前后逆序对数量的变化问题。采用三维偏序cdq分治和树状数组来高效地统计逆序对数目。

P3157 [CQOI2011]动态逆序对

题目描述

对于序列 A A A,它的逆序对数定义为满足 i < j i<j i<j,且 A i > A j A_i>A_j Ai>Aj的数对 ( i , j ) (i,j) (i,j)的个数。

1 1 1 n n n的一个排列,按照某种顺序依次删除 m m m个元素。

你的任务是在每次删除一个元素之前统计整个序列的逆序对数。

输入格式

输入第一行包括 n n n m m m,表示初始元素的个数和删除的元素个数。

第二行 n n n个元素表示初始序列。

第三行 m m m个数表示每次要删除元素的值。

输出格式

输出包含 m m m行,表示删除每个元素之前,该序列逆序对的个数。

输入样例
5 4
1 5 3 4 2
5 1 4 2
输出样例
5
2
2
1

数据范围

n ≤ 1 0 5 , m ≤ 5 × 1 0 4 n\leq 10^5,m\leq 5\times10^4 n105,m5×104

题解

前置知识: c d q cdq cdq分治

删除操作比较麻烦,所以我们可以倒着做,一开始有 n − m n-m nm个元素,依次加入 m m m个元素,求每加入一个元素之后,该序列逆序对的数量。

a i a_i ai表示第 i i i个数的值, t i t_i ti表示第 i i i个数插入序列的时间。先将开始的 n − m n-m nm个元素插入序列,再将 m m m个元素依次插入序列。

i i i插入序列后,序列的逆序对增加的数量为满足以下条件的 j j j的数量:

j < i , a j > a i , t j < t i j<i,a_j>a_i,t_j<t_i j<i,aj>ai,tj<ti或者 j > i , a j < a i , t j < t i j>i,a_j<a_i,t_j<t_i j>i,aj<ai,tj<ti

所以,我们可以先按 j < i , a j > a i , t j < t i j<i,a_j>a_i,t_j<t_i j<i,aj>ai,tj<ti为偏序做一次 c d q cdq cdq,再按 j > i , a j < a i , t j < t i j>i,a_j<a_i,t_j<t_i j>i,aj<ai,tj<ti为偏序做一次 c d q cdq cdq,就可以得出各点加入序列后对逆序对数量的贡献。这里用到了三维偏序 c d q cdq cdq,所以要用 c d q cdq cdq中还要用数状数组来维护答案。

最后按顺序将贡献累加,即可达到答案。时间复杂度为 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

code

#include<bits/stdc++.h>
using namespace std;
int n,m,vt=0,d[100005],tr[100005];
struct node{
    int x,id,t;
    long long ans;
}a[100005],b[100005];
bool cmp(node ax,node bx){
    return ax.t<bx.t;
}
int lb(int i){
    return i&(-i);
}
void add(int i,int t){
    while(i<=n){
        tr[i]+=t;i+=lb(i);
    }
}
int find(int i){
    int re=0;
    while(i){
        re+=tr[i];i-=lb(i);
    }
    return re;
}
void cdq1(int l,int r){
    if(l==r) return;
    int mid=(l+r)/2;
    cdq1(l,mid);cdq1(mid+1,r);
    int i=l,j=mid+1,k=l;
    while(i<=mid&&j<=r){
        if(a[i].id<a[j].id){
            add(a[i].x,1);
            b[k]=a[i];++i;++k;
        }
        else{
            a[j].ans+=find(n)-find(a[j].x-1);
            b[k]=a[j];++j;++k;
        }
    }
    while(i<=mid){
        add(a[i].x,1);b[k]=a[i];++i;++k;
    }
    while(j<=r){
        a[j].ans+=find(n)-find(a[j].x-1);
        b[k]=a[j];++j;++k;
    }
    for(int o=l;o<=mid;o++){
        add(a[o].x,-1);
    }
    for(int o=l;o<=r;o++) a[o]=b[o];
}
void cdq2(int l,int r){
    if(l==r) return;
    int mid=(l+r)/2;
    cdq2(l,mid);cdq2(mid+1,r);
    int i=l,j=mid+1,k=l;
    while(i<=mid&&j<=r){
        if(a[i].x<a[j].x){
            add(a[i].id,1);
            b[k]=a[i];++i;++k;
        }
        else{
            a[j].ans+=find(n)-find(a[j].id-1);
            b[k]=a[j];++j;++k;
        }
    }
    while(i<=mid){
        add(a[i].id,1);b[k]=a[i];++i;++k;
    }
    while(j<=r){
        a[j].ans+=find(n)-find(a[j].id-1);
        b[k]=a[j];++j;++k;
    }
    for(int o=l;o<=mid;o++){
        add(a[o].id,-1);
    }
    for(int o=l;o<=r;o++) a[o]=b[o];
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i].x);a[i].id=i;
    }
    for(int i=1,x;i<=m;i++){
        scanf("%d",&x);d[x]=m-i+1;
    }
    for(int i=1;i<=n;i++){
        if(!d[a[i].x]) d[a[i].x]=--vt;
        a[i].t=d[a[i].x];
    }
    sort(a+1,a+n+1,cmp);
    cdq1(1,n);
    sort(a+1,a+n+1,cmp);
    cdq2(1,n);
    sort(a+1,a+n+1,cmp);
    for(int i=2;i<=n;i++) a[i].ans+=a[i-1].ans;
    for(int i=n;i>=1;i--){
        if(a[i].t>0) printf("%lld\n",a[i].ans);
    }
    return 0;
}
# P3157 [CQOI2011] 动态逆序对 ## 题目描述 对于序列 $a$,它的逆序对数定义为集合 $$\{(i,j)| i<j \wedge a_i > a_j \}$$ 中的元素个数。 现在给出 $1\sim n$ 的一个排列,按照某种顺序依次删除 $m$ 个元素,你的任务是在每次删除一个元素**之前**统计整个序列的逆序对数。 ## 输入格式 第一行包含两个整数 $n$ 和 $m$,即初始元素的个数和删除的元素个数。 以下 $n$ 行,每行包含一个 $1 \sim n$ 之间的正整数,即初始排列。 接下来 $m$ 行,每行一个正整数,依次为每次删除的元素。 ## 输出格式 输出包含 $m$ 行,依次为删除每个元素之前,逆序对的个数。 ## 输入输出样例 #1 ### 输入 #1 ``` 5 4 1 5 3 4 2 5 1 4 2 ``` ### 输出 #1 ``` 5 2 2 1 ``` ## 说明/提示 【数据范围】 对于 $100\%$ 的数据,$1\le n \le 10^5$,$1\le m \le 50000$。 【样例解释】 删除每个元素之前的序列依次为: $$1,5,3,4,2$$ $$1,3,4,2$$ $$3,4,2$$ $$3,2$$ 为什么WA了 ```cpp #include <iostream> #include <algorithm> #include <vector> using namespace std; struct BIT { #define lowbit(x) x & (-x) vector<int> tr; BIT(int maxn) {tr.assign(maxn + 1, 0); } void add(int idx, int val) { for (; idx < tr.size(); idx += lowbit(idx)) tr[idx] += val; } int query(int idx) { int res = 0; for (; idx; idx -= lowbit(idx)) res += tr[idx]; return res; } }; class CDQ { public: void add(int x, int y, int z) { a.push_back(Node{x, y, z, 0, 0}); } void work(int ans[], int maxn) { b.assign(a.begin(), a.end()); sort(b.begin(), b.end()); for (int i = 0; i < b.size(); i++) { if (i == 0 || b[i - 1] < b[i]) c.push_back(b[i]); c.back().cnt++; } BIT tr(maxn); cdq(tr, 0, c.size() - 1); sort(c.begin(), c.end()); for (int i = 0; i < a.size(); i++) { auto tmp = lower_bound(c.begin(), c.end(), a[i]); ans[i + 1] += tmp->ans + tmp->cnt - 1; } } private: void cdq(BIT& tr, int l, int r) { if (l >= r) return ; int mid = l + r >> 1; cdq(tr, l, mid), cdq(tr, mid + 1, r); sort(&c[l], &c[mid] + 1, [](Node& p, Node& q) { return p.y < q.y; }); sort(&c[mid + 1], &c[r] + 1, [](Node& p, Node& q) { return p.y < q.y; }); int i = l, j = mid + 1; for (; j <= r; j++) { for (; i <= mid && c[i].y <= c[j].y; i++) tr.add(c[i].z, c[i].cnt); c[j].ans += tr.query(c[j].z); } for (int k = l; k < i; k++) tr.add(c[k].z, -c[k].cnt); } struct Node { int x, y, z, cnt, ans; bool operator < (const Node& rhs) const { if (x != rhs.x) return x < rhs.x; return y == rhs.y ? z < rhs.z : y < rhs.y; } }; vector<Node> a, b, c; }; const int MAX_N = 1e5 + 50; int n, m, a[MAX_N], p[MAX_N]; int tim[MAX_N], cnt[MAX_N], ans[MAX_N]; int main() { cin >> n >> m; for (int i = 1; i <= n; i++) cin >> a[i]; fill(tim + 1, tim + n + 1, m); for (int i = 1; i <= m; i++) cin >> p[i], tim[p[i]] = i; CDQ cdq; for (int i = 1; i <= n; i++) cdq.add(i, n - a[i], m - tim[i] + 1); cdq.work(cnt, m); for (int i = 1; i <= n; i++) ans[tim[i]] += cnt[i]; for (int i = m; i > 1; i--) ans[i - 1] += ans[i]; for (int i = 1; i <= m; i++) cout << ans[i] << '\n'; return 0; } ```
最新发布
08-12
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值