题解 P3157 【[CQOI2011]动态逆序对】

题目链接

Solution [CQOI2011]动态逆序对

题目大意:给定一个(n)个数的排列,依次删除(m)个元素,询问删除每个元素之前的逆序对数量

(cdq)分治


分析:对于这种依次删除元素的问题,我们的通常解法是时间倒流,顺序删除变逆序插入,那么问题就转化为了每插入一个数之后(对应删除之前)询问逆序对数量.

我们设元素(i)的时间戳为(T_i)(对于那些没有被删除的元素,(T_i = 0)),位置为(P_i),权值为(V_i)

那么,如果插入元素(j)后,元素(i)与元素(j)构成逆序对,它们需要满足

[egin{cases}T_i leq T_j quad ext{i要先于j插入才可以构成逆序对} \ P_i < P_j \ V_i > V_j quad ext{由于是排列,我们可以不考虑等号}end{cases} ]

或者

[egin{cases}T_i leq T_j \ P_i > P_j \ V_i < V_j end{cases} ]

那么这个问题就成了一个三维偏序问题了,由于不强制在线,我们考虑(cdq)分治,我们以第一种情况举例(第二种情况依葫芦画瓢类推即可)

我们先对三元组((T,P,V))按照第一维从小到大排序,然后按照第二维从小到大的顺序归并,因为你要统计的是第二维比这个数小的数里第三维比这个数大的数的个数(可能有点难理解,语文不好见谅),然后在数据结构查找第三维比当前数大的个数即可

第二种情况类推,我比较懒就写了两份(cdq)分治,反正复制粘贴也不累(雾

你以为到此问题就解决了?naive

我们求出的实际上是在时刻(t)插入元素(j)后新增的逆序对数量(从第一维的偏序就可以看出来这点),所以实际答案需要求一个前缀和

然后没什么坑点了,注意读入的是元素而不是下标

然后记得开(long;long)就好,虽然我也没有认真算过会不会炸

上代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 100;
int val[maxn],to[maxn],n,m;
namespace BIT{//树状数组,清空BIT我选择了打时间戳
    int f[maxn],t[maxn],n,tot;
    inline void init(int x){n = x;}
    inline int lowbit(int x){return x & (-x);}
    inline void clear(){tot++;}
    inline void add(int pos,int x){
        while(pos <= n){
            if(t[pos] < tot)f[pos] = 0,t[pos] = tot;
            f[pos] += x;
            pos += lowbit(pos);
        }
    }
    inline int query(int pos){
        int ret = 0;
        while(pos){
            if(t[pos] < tot)f[pos] = 0,t[pos] = tot;
            ret += f[pos];
            pos -= lowbit(pos);
        }
        return ret;
    }
    inline int query(int a,int b){return query(b) - query(a - 1);}
}
ll ans[maxn];
struct Node{//三元组,本来打算用tuple,但是担心常数还是选择手写
    int x,y,z;
    bool operator < (const Node &rhs)const{
        return x < rhs.x;
    }
};
namespace cdqa{//对应上文第一种情况
    Node val[maxn],tmp[maxn];
    void cdq(int a,int b){
        if(a == b)return;
        int mid = (a + b) >> 1;
        cdq(a,mid),cdq(mid + 1,b);
        int p1 = a,p2 = mid + 1,p = a;
        while(p1 <= mid && p2 <= b){
            if(val[p1].y < val[p2].y)BIT::add(val[p1].z,1),tmp[p++] = val[p1++];//按第二维从小到大的顺序归并
            else ans[val[p2].x] += BIT::query(val[p2].z + 1,n),tmp[p++] = val[p2++];
        }
        while(p1 <= mid)tmp[p++] = val[p1++];
        while(p2 <= b)ans[val[p2].x] += BIT::query(val[p2].z + 1,n),tmp[p++] = val[p2++];
        for(int i = a;i <= b;i++)val[i] = tmp[i];
        BIT::clear();
    }
}
namespace cdqb{//第二种情况
    Node val[maxn],tmp[maxn];
    void cdq(int a,int b){
        if(a == b)return;
        int mid = (a + b) >> 1;
        cdq(a,mid),cdq(mid + 1,b);
        int p1 = a,p2 = mid + 1,p = a;
        while(p1 <= mid && p2 <= b){
            if(val[p1].y > val[p2].y)BIT::add(val[p1].z,1),tmp[p++] = val[p1++];
            else ans[val[p2].x] += BIT::query(1,val[p2].z - 1),tmp[p++] = val[p2++];
        }
        while(p1 <= mid)tmp[p++] = val[p1++];
        while(p2 <= b)ans[val[p2].x] += BIT::query(1,val[p2].z - 1),tmp[p++] = val[p2++];
        for(int i = a;i <= b;i++)val[i] = tmp[i];
        BIT::clear();
    }
}
int main(){
#ifdef LOCAL
    freopen("fafa.in","r",stdin);
#endif
    scanf("%d %d",&n,&m);
    BIT::init(n);
    for(int i = 1;i <= n;i++)scanf("%d",&val[i]),to[val[i]] = i;//由于读入的是元素,所以我们需要做一个映射得到下标
    for(int i = 1;i <= n;i++){//读入
        cdqa::val[i].x = 0,cdqa::val[i].y = i,cdqa::val[i].z = val[i];
        cdqb::val[i].x = 0,cdqb::val[i].y = i,cdqb::val[i].z = val[i];
    }
    for(int pos,i = 1;i <= m;i++){//得到时间戳
        scanf("%d",&pos);
        cdqa::val[to[pos]].x = m - i + 1;
        cdqb::val[to[pos]].x = m - i + 1;
    }
    sort(cdqa::val + 1,cdqa::val + 1 + n);
    sort(cdqb::val + 1,cdqb::val + 1 + n);
    cdqa::cdq(1,n);
    cdqb::cdq(1,n);//计算
    for(int i = 1;i <= m;i++)ans[i] += ans[i - 1];//前缀和
    for(int i = m;i >= 1;i--)printf("%lld
",ans[i]);//询问的是删除之后的逆序对数量,因此需要倒序输出.没有询问一开始的数量因而不需要输出ans[0]
    return 0;
}
原文地址:https://www.cnblogs.com/colazcy/p/11515045.html