题目大意

给出一个序列 \(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. stm32f103中freertos的tasks基本使用案例及备忘

    基本实例   freetos的在stm32中使用踩了一些坑,事情做完了,就 做个备忘,希望能给后面的人一些借鉴. 先给出一个实际的例子吧. 启动代码 void task_create(void) { ...

  2. 212. 单词搜索 II

    Q: 给定一个二维网格 board 和一个字典中的单词列表 words,找出所有同时在二维网格和字典中出现的单词. 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中"相邻" ...

  3. MySQL-THINKPHP 商城系统一 商品模块的设计

    在此之前,先了解下关于SPU及SKU的知识 SPU是商品信息聚合的最小单位,是一组可复用.易检索的标准化信息的集合,该集合描述了一个产品的特性.通俗点讲,属性值.特性相同的商品就可以称为一个SPU. ...

  4. eclipse 添加主題

    在使用Eclipse过程中可能想更换下界面主题,此处介绍的是一款主题插件 Eclipse Color Theme 打开Eclipse,Help --> Eclipse Marketplace 在 ...

  5. Saber-图集

    PS:狙击手

  6. bugku 闪的好快

    这是一道二维码的题目.保存图片祭出神器StegSolve.然后Analysis->Frame Browser.这里发现是18张图.也就是18张图片. 我拿手机一个挨着一个扫的.扫出来的结果是SY ...

  7. drf解析模块,异常模块,响应模块,序列化模块

    复习 """ 1.接口:url+请求参数+响应参数 Postman发送接口请求的工具 method: GET url: https://api.map.baidu.com ...

  8. next.config.js

    const configs = { // 编译文件的输出目录 distDir: 'dest', // 是否给每个路由生成Etag generateEtags: true, // 页面内容缓存配置 on ...

  9. c++中sort函数调用报错Expression : invalid operator <的内部原理

    当我们调用sort函数进行排序时,中的比较函数如果写成如下 bool cmp(const int &a, const int &b) { if(a!=b) return a<b; ...

  10. 浅谈分治 —— 洛谷P1228 地毯填补问题 题解

    如果想看原题网址的话请点击这里:地毯填补问题 原题: 题目描述 相传在一个古老的阿拉伯国家里,有一座宫殿.宫殿里有个四四方方的格子迷宫,国王选择驸马的方法非常特殊,也非常简单:公主就站在其中一个方格子 ...