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


水一水QAQ

题目链接; \([P3157]\) \([BZOJ3295]\)

【题目描述】

对于一个序列 \(a\) ,他的逆序对数定义为满足 \(i<j\),且 \(a_i>a_j\) 的数对 \((i,j)\) 的个数。

给出 \(1\) 到 \(n\) 的一个排列,依次删除 \(m\) 个元素,在每次删除一个元素之前统计出序列的逆序对数。

【输入】

输入第一行包含两个整数 \(n\) 和 \(m\),以下 \(n\) 行包含 \(n\)个小于等于 \(n\) 的正整数,即初始序列。随后 \(m\) 行共 \(m\) 个正整数,表示要删除的元素。

【输出】

输出包含 \(m\) 行,表示每次删除某个元素之前,逆序对的个数。

【样例】

输入:
5 4
1
5
3
4
2
5
1
4
2
样例输出:
5
2
2
1
样例解释:
初始序列:(1,5,3,4,2)
逆序对个数为:5,删去5后的序列:(1,3,4,2)
逆序对个数为:2,删去1后的序列:(3,4,2)
逆序对个数为:2,删去4后的序列:(3,2)
逆序对个数为:1,删去2后的序列:(3)

【数据范围】

\(100\%\) \(n \leqslant 1e5,m \leqslant 5e4\)


【分析】

这道题的解法似乎比较多,但本蒟蒻只会一种【主席树】。

首先考虑一下不带修改的静态求逆序对,直接用树状数组随便搞搞就行了:

用 \(a1[i]\) 表示位于 \(i\) 前面大于 \(a[i]\) 的元素的个数,

用 \(a2[i]\) 表示位于 \(i\) 后面小于 \(a[i]\) 的元素的个数,

答案则是: \(\sum_{i=1}^n a1[i]\)

带了修改后,问题变得略微麻烦了一点,首先按静态求逆序对的方法计算出初始序列的逆序对个数,每删除一个数 \(a[i]\),它所造成的影响就是:使序列中的逆序对个数减少了 \(a1[i]+a2[i]\) 个,于是只需要在删除某个数后计算一下贡献值即可。

但如果 \(a1[i]\) 或 \(a2[i]\) 统计的数中有已经删除过的怎么办?那相当于是对于同一对逆序数减了两次(删除前面的数时已经减去了这一对逆序数,消去了它的贡献,删除后面的数时又减了一次),所以在计算删除 \(a[i]\) 的贡献时还需要加上在已经删除的数中可以与 \(a[i]\) 形成的逆序对数,可以用主席树维护已经删除的数。

\(【Code】\)

#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<algorithm>
#include<cstring>
#include<cstdio>
#define mid (L+R>>1)
#define Re register int
#define LL long long
#define F(i,a,b) for(Re i=a;i<=b;++i)
using namespace std;
const int N=1e5+3,M=5e4+3;LL ans;
int x,n,T,fu,cnt,C[N],a[N],b[N],a1[N],a2[N],pt[N],ptl[50],ptr[50];
struct QAQ{int g,lp,rp;}tree[N*50];//空间一定要开足
inline void in(int &x){
x=fu=0;char c=getchar();
while(c<'0'||c>'9')fu|=c=='-',c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=fu?-x:x;
}
inline void out(LL x){if(x>9)out(x/10);putchar(x%10+'0');}
inline int ask(Re x){Re ans=0;while(x)ans+=C[x],x-=x&-x;return ans;}//树状数组的单点修改
inline void add(Re x){while(x<=n)++C[x],x+=x&-x;}//树状数组的区间查询
inline int ask_(Re L,Re R,Re x,Re rule){
Re tl=0,tr=0,ans=0;
for(Re i=L-1;i;i-=i&-i)ptl[++tl]=pt[i];
for(Re i=R;i;i-=i&-i)ptr[++tr]=pt[i];
L=1,R=n;
while(L<R)
if(x<=mid){//若x在左子树,则右子树全在x的后面
if(rule){//查询已删除的数中在i后面且比a[i]小的数
F(i,1,tl)ans-=tree[tree[ptl[i]].rp].g;//累加上右子树的所有信息
F(i,1,tr)ans+=tree[tree[ptr[i]].rp].g;
}
F(i,1,tl)ptl[i]=tree[ptl[i]].lp;
F(i,1,tr)ptr[i]=tree[ptr[i]].lp;
R=mid;//进入左子树的查询
}
else{//若x在右子树,则左子树全在x的前面
if(!rule){//查询已删除的数中在i前面且比a[i]大的数
F(i,1,tl)ans-=tree[tree[ptl[i]].lp].g;//累加上右子树的所有信息
F(i,1,tr)ans+=tree[tree[ptr[i]].lp].g;
}
F(i,1,tl)ptl[i]=tree[ptl[i]].rp;
F(i,1,tr)ptr[i]=tree[ptr[i]].rp;
L=mid+1;//进入右子树的查询
}
return ans;
}
inline void change(Re &p,Re L,Re R,Re x){//【单点修改】
if(!p)p=++cnt;++tree[p].g;
if(L==R)return;
if(x<=mid)change(tree[p].lp,L,mid,x);
else change(tree[p].rp,mid+1,R,x);
}
int main(){
in(n),in(T);
F(i,1,n){//用树状数组预处理a1[]
in(a[i]),b[a[i]]=i;
ans+=(a1[i]=ask(n)-ask(a[i]));//预处理出初始序列的逆序对数
add(a[i]);
}
memset(C,0,sizeof(C));
F(j,1,n){//用树状数组预处理a2[]
Re i=n-j+1;
a2[i]=ask(a[i]-1);
add(a[i]);
}
while(T--){
out(ans);puts("");
in(x);x=b[x];//要删除 x ,b[x]为它原本的位置
ans-=a1[x]+a2[x]-ask_(1,x-1,a[x],1)-ask_(x+1,n,a[x],0);
Re i=x;
while(i<=n)change(pt[i],1,n,a[x]),i+=i&-i;//删除了数后更新一下线段树
}
}

双倍经验: \([P1393]\)

这道题的数据范围变小了一些,但给定的序列需要离散化,并且题目改成了删除位于 \(x\) 上的数,注意一下这几个地方,随便搞搞就完事了。

\(【Code】\)

#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<algorithm>
#include<cstring>
#include<cstdio>
#define mid (L+R>>1)
#define Re register int
#define LL long long
#define F(i,a,b) for(Re i=a;i<=b;++i)
using namespace std;
const int N=4e4+3;LL ans;
int x,n,m,T,fu,cnt,C[N],a[N],a1[N],a2[N],pt[N],pos[N],ptl[50],ptr[50];
struct QAQ{int g,lp,rp;}tree[N*50];//空间一定要开足
struct QWQ{int i,x;inline bool operator<(QWQ b)const{return x<b.x;}}Q[N];
inline void in(int &x){
x=fu=0;char c=getchar();
while(c<'0'||c>'9')fu|=c=='-',c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=fu?-x:x;
}
inline void out(LL x){if(x>9)out(x/10);putchar(x%10+'0');}
inline int ask(Re x){Re ans=0;while(x)ans+=C[x],x-=x&-x;return ans;}//树状数组的单点修改
inline void add(Re x){while(x<=n)++C[x],x+=x&-x;}//树状数组的区间查询
inline int ask_(Re L,Re R,Re x,Re rule){
Re tl=0,tr=0,ans=0;
for(Re i=L-1;i;i-=i&-i)ptl[++tl]=pt[i];
for(Re i=R;i;i-=i&-i)ptr[++tr]=pt[i];
L=1,R=n;
while(L<R)
if(x<=mid){//若x在左子树,则右子树全在x的后面
if(rule){//查询已删除的数中在i后面且比a[i]小的数
F(i,1,tl)ans-=tree[tree[ptl[i]].rp].g;//累加上右子树的所有信息
F(i,1,tr)ans+=tree[tree[ptr[i]].rp].g;
}
F(i,1,tl)ptl[i]=tree[ptl[i]].lp;
F(i,1,tr)ptr[i]=tree[ptr[i]].lp;
R=mid;//进入左子树的查询
}
else{//若x在右子树,则左子树全在x的前面
if(!rule){//查询已删除的数中在i前面且比a[i]大的数
F(i,1,tl)ans-=tree[tree[ptl[i]].lp].g;//累加上右子树的所有信息
F(i,1,tr)ans+=tree[tree[ptr[i]].lp].g;
}
F(i,1,tl)ptl[i]=tree[ptl[i]].rp;
F(i,1,tr)ptr[i]=tree[ptr[i]].rp;
L=mid+1;//进入右子树的查询
}
return ans;
}
inline void change(Re &p,Re L,Re R,Re x){//【单点修改】
if(!p)p=++cnt;++tree[p].g;
if(L==R)return;
if(x<=mid)change(tree[p].lp,L,mid,x);
else change(tree[p].rp,mid+1,R,x);
}
int main(){
in(m),in(T);
F(i,1,m)in(a[i]),Q[++n].x=a[i],Q[n].i=i;
sort(Q+1,Q+n+1);
F(i,1,n)pos[Q[i].i]=i;
F(i,1,m){//用树状数组预处理a1[]
ans+=(a1[i]=ask(n)-ask(pos[i]));//预处理出初始序列的逆序对数
add(pos[i]);
}
memset(C,0,sizeof(C));
F(j,1,m){//用树状数组预处理a2[]
Re i=m-j+1;
a2[i]=ask(pos[i]-1);
add(pos[i]);
}
out(ans),putchar(' ');
while(T--){
in(x);//要删除 x 这个位置上的数
ans-=a1[x]+a2[x]-ask_(1,x-1,pos[x],1)-ask_(x+1,n,pos[x],0);
Re i=x;
while(i<=n)change(pt[i],1,n,pos[x]),i+=i&-i;//删除了数后更新一下线段树
out(ans),putchar(' ');
}
}

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

  1. 洛谷P3157 动态逆序对 [CQOI2011] cdq分治

    正解:cdq分治 解题报告: 传送门! 长得有点像双倍经验还麻油仔细看先放上来QwQ! 这题首先想到的就直接做逆序对,然后记录每个点的贡献,删去就减掉就好 但是仔细一想会发现布星啊,如果有一对逆序对的 ...

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

    [BZOJ3295][Cqoi2011]动态逆序对 Description 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依 ...

  3. bzoj3295 [Cqoi2011]动态逆序对 cdq+树状数组

    [bzoj3295][Cqoi2011]动态逆序对 2014年6月17日4,7954 Description 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数. ...

  4. bzoj3295 洛谷P3157、1393 动态逆序对——树套树

    题目:bzoj3295 https://www.lydsy.com/JudgeOnline/problem.php?id=3295 洛谷 P3157(同一道题) https://www.luogu.o ...

  5. P3157 [CQOI2011]动态逆序对(树状数组套线段树)

    P3157 [CQOI2011]动态逆序对 树状数组套线段树 静态逆序对咋做?树状数组(别管归并QWQ) 然鹅动态的咋做? 我们考虑每次删除一个元素. 减去的就是与这个元素有关的逆序对数,介个可以预处 ...

  6. P3157 [CQOI2011]动态逆序对

    P3157 [CQOI2011]动态逆序对 https://www.luogu.org/problemnew/show/P3157 题目描述 对于序列A,它的逆序对数定义为满足i<j,且Ai&g ...

  7. [BZOJ3295][Cqoi2011]动态逆序对 CDQ分治&树套树

    3295: [Cqoi2011]动态逆序对 Time Limit: 10 Sec  Memory Limit: 128 MB Description 对于序列A,它的逆序对数定义为满足i<j,且 ...

  8. 洛谷 P3157 [CQOI2011]动态逆序对 解题报告

    P3157 [CQOI2011]动态逆序对 题目描述 对于序列\(A\),它的逆序对数定义为满足\(i<j\),且\(A_i>A_j\)的数对\((i,j)\)的个数.给\(1\)到\(n ...

  9. BZOJ3295 [Cqoi2011]动态逆序对 —— CDQ分治

    题目链接:https://vjudge.net/problem/HYSBZ-3295 3295: [Cqoi2011]动态逆序对 Time Limit: 10 Sec  Memory Limit: 1 ...

随机推荐

  1. SqlServer2008必须开启哪些服务

    SQL Server 2008 大概有下面这些服务 SQL Active Directory Helper 服务支持与 Active Directory 的集成SQL Full-text Filter ...

  2. HDU 4598 Difference

    Difference Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65535/65535 K (Java/Others)Total ...

  3. poj 2135最小费用最大流

    最小费用最大流问题是经济学和管理学中的一类典型问题.在一个网络中每段路径都有"容量"和"费用"两个限制的条件下,此类问题的研究试图寻找出:流量从A到B,如何选择 ...

  4. vue.js中的路由vue-router2.0使用

    在我们平时工作中,我们有时候会有需求,按照不同的规则,加载不同的组件,页面不去跳转,常见的操作是ajax的异步操作,实现局部刷新加载新数据 在vue中,我们写了很多不同的组件,这时候,实现不刷新调用新 ...

  5. 一文教你用 Neo4j 快速构建明星关系图谱

    更多有趣项目及代码见于:DesertsX/gulius-projects 前言 本文将带你用 neo4j 快速实现一个明星关系图谱,因为拖延的缘故,正好赶上又一年的4月1日,于是将文中的几个例子顺势改 ...

  6. G - 免费馅饼 基础DP

    都说天上不会掉馅饼,但有一天gameboy正走在回家的小径上,忽然天上掉下大把大把的馅饼.说来gameboy的人品实在是太好了,这馅饼别处都不掉,就掉落在他身旁的10米范围内.馅饼如果掉在了地上当然就 ...

  7. Nginx 源码

    http://blog.sina.com.cn/s/articlelist_1834459124_1_1.html http://tengine.taobao.org/book/ https://gi ...

  8. Redis: 改变HomeBrew安装的数据库文件目录

    vi /usr/local/etc/redis.conf 修改dir "/Volumes/KG's Big YO/Documents/redis_data" 最后,启动Redis: ...

  9. cocos2d-x大版本号3.1系列一

    本人博客,欢迎转载:http://blog.csdn.net/dawn_moon 项目忙完了.继续写我的博客.去cocos2d-x的官网看了下,不出所料.又有惊喜啊.3.0经过几个版本号的迭代,最终迎 ...

  10. java中commons-beanutils的介绍

    1.   概述 commons-beanutil开源库是apache组织的一个基础的开源库.为apache中很多类提供工具方法.学习它是学习其它开源库实现的基础. Commons-beanutil中包 ...