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

题目链接

Solution [CQOI2011]动态逆序对

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

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

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

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

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

或者
\[\begin{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\n",ans[i]);//询问的是删除之后的逆序对数量,因此需要倒序输出.没有询问一开始的数量因而不需要输出ans[0]
    return 0;
}

原文地址:https://www.cnblogs.com/colazcy/p/11515045.html

时间: 2024-09-30 13:37:09

题解 P3157 【[CQOI2011]动态逆序对】的相关文章

题解-P3157 [CQOI2011]动态逆序对

树状数组套线段树,用动态开点线段树.此类就这一题放在这吧. 程序写的太丑了,不要介意. #include<iostream> #include<cstdio> using namespace std; typedef long long i64; const int N=1e5+5; const int S=1e7+7; int a[N],p[N]; int q[N]; bool b[N]; int r[N]; int cnt,x[S],c[S][2]; i64 ans[N]; v

P3157 [CQOI2011]动态逆序对

P3157 [CQOI2011]动态逆序对 https://www.luogu.org/problemnew/show/P3157 题目描述 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数. 输入输出格式 输入格式: 输入第一行包含两个整数n和m,即初始元素的个数和删除的元素个数.以下n行每行包含一个1到n之间的正整数,即初始排列.以下m行每行一个正整数,依次

P3157 [CQOI2011]动态逆序对 (CDQ解决三维偏序问题)

P3157 [CQOI2011]动态逆序对 题目描述 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数. 输入格式 输入第一行包含两个整数n和m,即初始元素的个数和删除的元素个数.以下n行每行包含一个1到n之间的正整数,即初始排列.以下m行每行一个正整数,依次为每次删除的元素. 输出格式 输出包含m行,依次为删除每个元素之前,逆序对的个数. 输入输出样例 输入

[Luogu P3157][CQOI2011]动态逆序对 (树套树)

题面 传送门:[CQOI2011]动态逆序对 Solution 一开始我看到pty巨神写这套题的时候,第一眼还以为是个SB题:这不直接开倒车线段树统计就完成了吗? 然后冷静思考了一分钟,猛然发现单纯的线段树并不能解决这个问题,好像还要在外面再套上一颗树. 这就很shit了.你问我资磁不资磁树套树,我是不资磁的,树套树是暴力数据结构,我能资磁吗? 很不幸,昨天现实狠狠地打了我一脸:时间不够开新坑的,不切题又浑身难受,找了半天题,还是把这道题拉了出来(哈,真香) 不扯淡了,这题还是很显然的. 考虑开

luogu P3157 [CQOI2011]动态逆序对(CDQ分治)

题目描述 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数. 输入输出格式 输入格式: 输入第一行包含两个整数n和m,即初始元素的个数和删除的元素个数.以下n行每行包含一个1到n之间的正整数,即初始排列.以下m行每行一个正整数,依次为每次删除的元素. 输出格式: 输出包含m行,依次为删除每个元素之前,逆序对的个数. 题解 我们发现一个数的贡献,就是就是t'<

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

LUOGU P3157 [CQOI2011]动态逆序对(CDQ 分治)

传送门 解题思路 cdq分治,将位置看做一维,修改时间看做一维,权值看做一维,然后就转化成了三维偏序,用排序+cdq+树状数组.注意算删除贡献时要做两次cdq,分别算对前面和后面的贡献. #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int MAXN = 100005; const int MAXM =

主席树初探 &amp; bzoj 3295: [Cqoi2011] 动态逆序对 题解

[原题] 3295: [Cqoi2011]动态逆序对 Time Limit: 10 Sec  Memory Limit: 128 MB Submit: 778  Solved: 263 [Submit][Status] Description 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数. Input 输入第一行包含两个整数n和m,即初始元素的个数和删除的元

【BZOJ3295】[Cqoi2011]动态逆序对 cdq分治

[BZOJ3295][Cqoi2011]动态逆序对 Description 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数. Input 输入第一行包含两个整数n和m,即初始元素的个数和删除的元素个数.以下n行每行包含一个1到n之间的正整数,即初始排列.以下m行每行一个正整数,依次为每次删除的元素. Output 输出包含m行,依次为删除每个元素之前,逆序对的