题目大意

给出一个序列 \(h\),支持交换其中的两数,求出每一时刻的逆序对个数.

分析

求逆序对是 \(O(N\log_2N)\) 的,有 \(M\) 个操作,如果暴力求的话时间复杂度就是 \(O(MN\log_2N)\) 虽然数据范围不大,但是还是可能因为评测机浮动而TLE,所以就不要想着折腾这些东西了,还是要用一些正经点的方法去过这种题.

求逆序对的方法大致可以分成两种:

  1. 用一些数据结构维护大于某个数的数在这个数之前出现过几次,只需要将这些数一个一个放入就好了,优点很明显,可以计算出每个数对于结果的贡献,但是缺点也很致命,常数较大,如luogu上的模板题就过不了了.
  2. 利用归并排序的性质来计算,但是这个方法中的每个数的贡献是无法单独计算的,跑起来确实会比第一种方法快.

第二种方法用在本题显然不合适,所以本题需要用第一种方法.

先不考虑交换两数有什么特殊的性质,单纯考虑删除一个数对于逆序对个数的影响(P3157 [CQOI2011]动态逆序对).

因为是删除一个数,其影响到的只有其他数与它产生的逆序对,而产生逆序对的条件就是 \(j<i\) 且 \(a_j>a_i\),所以在这个数前面且大于它的数会和它产生一个逆序对,在这个数后面且小于它的数也会和它产生一个逆序对,那么问题就变成了计算前面有多少大于它的数,后面有多少小于它的数,不考虑范围的话可以直接用权值线段树解决,然而这里是一个区间问题+单点修改,那么就很容易想到利用树状数组维护前缀每个数出现的次数,只要差分一下就可以得出结果.

至于需要重新加上一个数那还是一样,只要找到前面大于这个数的个数,后面小于这个数的个数,相加就是这个数对于这个序列的逆序对个数产生的贡献,因为树状数组维护的是前缀,所以交换两数带来的良好性质自然也没什么用了,但是,为了让这个没什么用的性质看似有用一点,还是放一道可以用这个性质的题.

代码

#include<bits/stdc++.h>
#define REP(i,first,last) for(int i=first;i<=last;++i)
#define DOW(i,first,last) for(int i=first;i>=last;--i)
using namespace std;
const int MAXN=114514;
int N,M;
int arr[MAXN];
int tot=0;
map<int,int>Hash;//用于离散化
int sor[MAXN];//用于离散化
int root[MAXN];//树状数组上每个位置的线段树的根节点
long long answer=0;
//线段树部分
struct SegmentTree
{
int sum,lson,rson;
}sgt[MAXN*32];
int sgt_cnt=0;
#define LSON sgt[now].lson
#define RSON sgt[now].rson
#define MIDDLE ((left+right)>>1)
#define LEFT LSON,left,MIDDLE
#define RIGHT RSON,MIDDLE+1,right
void PushUp(int now)//合并信息
{
sgt[now].sum=sgt[LSON].sum+sgt[RSON].sum;
}
void Updata(int num,int add,int &now,int left=1,int right=tot)//修改操作,和普通动态开点权值线段树相同
{
if(num<left||right<num)//不包含修改位置就返回
{
return;
}
if(!now)//如果当前位置没有节点就新建一个节点
{
now=++sgt_cnt;
}
if(left==right)//到叶节点就直接修改
{
sgt[now].sum+=add;
return;
}
//继续修改
Updata(num,add,LEFT);
Updata(num,add,RIGHT);
PushUp(now);
}
int num_add,num_dec;
int add_sgt[MAXN],dec_sgt[MAXN];//树状数组来维护,需要用到差分,需要记录下当前需要加上的线段树的当前根节点的编号,以及需要减去的线段树的当前编号
int GetSum()//得到当期范围中的数的个数,和树状数组计算区间和同理
{
int result=0;
REP(i,1,num_add)
{
result+=sgt[add_sgt[i]].sum;
}
REP(i,1,num_dec)
{
result-=sgt[dec_sgt[i]].sum;
}
return result;
}
//左右子树中的数的个数计算方法同理
int GetSumL()
{
int result=0;
REP(i,1,num_add)
{
result+=sgt[sgt[add_sgt[i]].lson].sum;
}
REP(i,1,num_dec)
{
result-=sgt[sgt[dec_sgt[i]].lson].sum;
}
return result;
}
int GetSumR()
{
int result=0;
REP(i,1,num_add)
{
result+=sgt[sgt[add_sgt[i]].rson].sum;
}
REP(i,1,num_dec)
{
result-=sgt[sgt[dec_sgt[i]].rson].sum;
}
return result;
}
//将当前的根节点变为左子节点
void GetRootL()
{
REP(i,1,num_add)
{
add_sgt[i]=sgt[add_sgt[i]].lson;
}
REP(i,1,num_dec)
{
dec_sgt[i]=sgt[dec_sgt[i]].lson;
}
}
//变为右子节点
void GetRootR()
{
REP(i,1,num_add)
{
add_sgt[i]=sgt[add_sgt[i]].rson;
}
REP(i,1,num_dec)
{
dec_sgt[i]=sgt[dec_sgt[i]].rson;
}
}
int QuerySmall(int num,int left=1,int right=tot)//查询小于的数的个数,计算方法和权值线段树同理,不多讲
{
if(num<=left)
{
return 0;
}
if(right<num)
{
return GetSum();
}
if(num<=MIDDLE)
{
GetRootL();
return QuerySmall(num,left,MIDDLE);
}
int result=GetSumL();
GetRootR();
return result+QuerySmall(num,MIDDLE+1,right);
}
int QueryBig(int num,int left=1,int right=tot)//计算大于的数的个数,同理
{
if(right<=num)
{
return 0;
}
if(left>num)
{
return GetSum();
}
if(MIDDLE+1<=num)
{
GetRootR();
return QueryBig(num,MIDDLE+1,right);
}
int result=GetSumR();
GetRootL();
return result+QueryBig(num,left,MIDDLE);
}
#undef LSON
#undef RSON
#undef MIDDLE
#undef LEFT
#undef RIGHT
int Lowbit(int now)//树状数组要用的lowbit
{
return now&-now;
}
void BeforeQuery(int left,int right)//在查询前的预处理,将需要加上的线段树的根节点和需要减去的线段树的根节点编号记录下来
{
num_add=0,num_dec=0;
for(int now=right;now;now-=Lowbit(now))
{
add_sgt[++num_add]=root[now];
}
for(int now=left-1;now;now-=Lowbit(now))
{
dec_sgt[++num_dec]=root[now];
}
}
int Small(int num,int left,int right)//查询区间内小于的数的个数
{
BeforeQuery(left,right);
return QuerySmall(num);
}
int Big(int num,int left,int right)//查询区间内大于的数的个数
{
BeforeQuery(left,right);
return QueryBig(num);
}
void Change(int p1,int p2)//交换两数,就是删掉一个数,再放上一个数的操作最两遍
{
int ha=arr[p1];
int hb=arr[p2];
answer-=Big(ha,1,p1-1)+Small(ha,p1+1,N);//删除一个数所减去的贡献
for(int now=p1;now<=N;now+=Lowbit(now))//在线段树中减去这个数
{
Updata(ha,-1,root[now]);
}
answer+=Big(hb,1,p1-1)+Small(hb,p1+1,N);//同理加上这个数
for(int now=p1;now<=N;now+=Lowbit(now))
{
Updata(hb,1,root[now]);
}
answer-=Big(hb,1,p2-1)+Small(hb,p2+1,N);
for(int now=p2;now<=N;now+=Lowbit(now))
{
Updata(hb,-1,root[now]);
}
answer+=Big(ha,1,p2-1)+Small(ha,p2+1,N);
for(int now=p2;now<=N;now+=Lowbit(now))
{
Updata(ha,1,root[now]);
}
swap(arr[p1],arr[p2]);
}
int main()
{
scanf("%d",&N);
REP(i,1,N)
{
scanf("%d",&arr[i]);
sor[i]=arr[i];
}
sort(sor+1,sor+1+N);//离散化
sor[0]=114514233;
REP(i,1,N)
{
if(sor[i]!=sor[i-1])
{
Hash[sor[i]]=++tot;
}
}
REP(i,1,N)//逆序对的计算中只需要考虑相对大小,所以不需要保留原来的值
{
arr[i]=Hash[arr[i]];
}
REP(i,1,N)//建树
{
for(int now=i;now<=N;now+=Lowbit(now))
{
Updata(arr[i],1,root[now]);
}
}
REP(i,2,N)//计算最开始的逆序对
{
answer+=Big(arr[i],1,i-1);
}
printf("%lld\n",answer);//记得输出最开始的逆序对个数
scanf("%d",&M);
int l,r;
REP(i,1,M)
{
scanf("%d%d",&l,&r);
Change(l,r);
printf("%lld\n",answer);
}
return 0;
}

「Luogu P1975 [国家集训队]排队」的更多相关文章

  1. P1975 [国家集训队]排队

    题目链接 题意分析 我们考虑 交换两个数\([le,ri]\)的贡献 减少的逆序对数\([le,ri]\)中小于\(num[le]\)以及大于\(num[ri]\)的数 增加的\([le,ri]\)中 ...

  2. P1975 [国家集训队]排队 线段树套平衡树维护动态逆序对查询

    $ \color{#0066ff}{ 题目描述 }$ 排排坐,吃果果,生果甜嗦嗦,大家笑呵呵.你一个,我一个,大的分给你,小的留给我,吃完果果唱支歌,大家乐和和. 红星幼儿园的小朋友们排起了长长地队伍 ...

  3. 洛谷 P1975 [国家集训队]排队 Lebal:块内排序+树状数组

    题目描述 排排坐,吃果果,生果甜嗦嗦,大家笑呵呵.你一个,我一个,大的分给你,小的留给我,吃完果果唱支歌,大家乐和和. 红星幼儿园的小朋友们排起了长长地队伍,准备吃果果.不过因为小朋友们的身高有所区别 ...

  4. luogu P2757 [国家集训队]等差子序列

    题目链接 luogu P2757 [国家集训队]等差子序列 题解 线段树好题 我选择暴力 代码 // luogu-judger-enable-o2 #include<cstdio> inl ...

  5. luogu P2619 [国家集训队2]Tree I

    题目链接 luogu P2619 [国家集训队2]Tree I 题解 普通思路就不说了二分增量,生成树check 说一下坑点 二分时,若黑白边权有相同,因为权值相同优先选白边,若在最有增量时出现黑白等 ...

  6. 【LG1975】[国家集训队]排队

    [LG1975][国家集训队]排队 题面 洛谷 题解 又是一个偏序问题 显然\(CDQ\) 交换操作不好弄怎么办? 可以看成两次删除两次插入 排序问题要注意一下 代码 #include <ios ...

  7. Luogu-1975 [国家集训队]排队

    Luogu-1975 [国家集训队]排队 题面 Luogu-1975 题解 题意:给出一个长度为n的数列以及m个交换两个数的操作,问每次操作后逆序对数量 时间,下标和数的大小三维偏序,,,把交换操作看 ...

  8. [Luogu P1829] [国家集训队]Crash的数字表格 / JZPTAB (莫比乌斯反演)

    题面 传送门:洛咕 Solution 调到自闭,我好菜啊 为了方便讨论,以下式子\(m>=n\) 为了方便书写,以下式子中的除号均为向下取整 我们来颓柿子吧qwq 显然,题目让我们求: \(\l ...

  9. Luogu P1297 [国家集训队]单选错位

    P1297 [国家集训队]单选错位 题目背景 原 <网线切割>请前往P1577 题目描述 gx和lc去参加noip初赛,其中有一种题型叫单项选择题,顾名思义,只有一个选项是正确答案.试卷上 ...

随机推荐

  1. CTF_论剑场 头像

    首先打开链接发现这个链接是一个头像 然后下载这个头像 通过hxd分析一下 在编辑中查找 flag 然后发现flag是一个用base64 加密的一串文字 然后我们将这串文字 解密 然后再通过md5 32 ...

  2. Spring Cloud netflix feign【服务间通信】

    一.简介 1,进程间通讯的本质是交换消息 2,服务间通信的两种方式 (1)RESTFul风格 (2)RPC风格 (3)两种风格的比较 3.基于RESTFul风格服务调用模型 4.基于Spring Cl ...

  3. 【音乐欣赏】《In The End》 - Taska Black / Aviella

    曲名:In The End 作者:Taska Black .Aviella [00:00.00] 作曲 : Joachim Gorrebeeck/Aviella Winder [00:11.48]La ...

  4. 【读书笔记】--《编写高质量iOS与OS X代码的52个有效方法》

    1.Objective-C 起源: 在 C 语言基础上添加了面向对象特性,是 C 语言的超集.Objective-C 由 SmallTalk 语言演变过来,使用消息结构,运行环境由运行环境决定. OC ...

  5. UseIIS

    asp.net core webapi的program.cs 文件中,要加上 使用IIS进程内,可以大幅提高处理速度

  6. Vue - 实现双击显示编辑框;自动聚焦点击的显示框;点击编辑框外的地方,隐藏编辑框

    实现这三个功能的踩坑记录. 1. 需求 在Vue中,有一个input, 双击时编辑文本,点击该input节点外的其他地方,则取消编辑. 那么这里有三个要实现的地方 第一是双击显示编辑框. 第二是自动聚 ...

  7. 6_12 油田(UVa572)<图的连通块DFS>

    有一家石油公司负责探勘某块地底下的石油含量,这块地是矩行的,并且为了探勘的方便被切割为许多小块.然后使用仪器对每个小块去探勘.含有石油的小块称为一个pocket.假如两个pocket相连,则这两个po ...

  8. 【Python pymysql】

    " 目录 关于sql注入 用户存在,绕过密码 用户不存在,绕过用户与密码 解决sql注入问题 commit() 增 改 删 查询数据库 fetchone() fetchall() fetch ...

  9. SpringCloud全家桶学习之Feign负载均衡----Feign(四)

    一.Feign概述 (1)Feign是什么? 官网地址:https://projects.spring.io/spring-cloud/spring-cloud.html#spring-cloud-f ...

  10. 关于 checkbox 的一些操作

    获取checkbox选中的状态 $("#checkbox").is(":checked"); 设置 checkbox 的状态 $("#checkbox ...