逆序对 线段树&树状数组 (重制版)
逆序对的定义:长度为n的数组a,求满足i<j时a[i]>a[j]条件的数对个数。
第一次接触这种问题的人可能是更先想到的是n^2去暴力数前面有几个比他大的数。
int main()
{
int n;
while(~scanf("%d", &n), n) {
ans = ;
for(int i = ; i <= n; i++)
scanf("%d", &a[i]);
for(int i = ; i <= n; i+=)
for(int j = ; j < i; j+=)
if(a[j] > a[i]) ans++;
printf("%d", ans);
}
return ;
}
n^2算法就是数一下前面有多少个数比现在这个数大 这样全部跑完只后就是逆序数了。
其中重点是 前面有多少个数比现在这个数大
但是每次从1 fo r一遍到i的位置太浪费时间了
所以我们可以用线段树来优化这个数数过程
#include<bits/stdc++.h>
using namespace std;
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define LL long long
const int N=;
int sum[N<<], a[N];
LL ans;
void Update(int c, int l, int r,int rt) {
if(l == r) {
sum[rt]++;
return;
}
int m = l+r >> ;
if(c <= m) Update(c,lson);
else Update(c,rson);
sum[rt]=sum[rt<<]+sum[rt<<|];
}
LL Query(int L, int R, int l, int r, int rt) {
if(L <= l && r <= R)
return sum[rt];
int m = l+r >> ;
LL cnt = ;
if(L <= m) cnt+=Query(L,R,lson);
if(m < R) cnt += Query(L,R,rson);
return cnt;
}
int main() {
int n;
while(~scanf("%d", &n), n){
ans = ;
memset(sum, , sizeof(sum));
for(int i = ; i <= n; i++){
scanf("%d", &a[i]);
}
for(int i = ; i <= n; i++) {
ans += Query(a[i],n,,n,);
Update(a[i],,n,);
}
printf("%d\n", ans);
}
return ;
}
线段树算法的在求逆序对的关键就是将出现过的数对应的位置标记一下(+1)
假设 i=k时, 查询一下区间 [a[k], n] 的区间和, 这个和就是(j < k && a[j] > a[k]) 的数目
然后在a[k] 的位置 +1
重复这个过程就能求出解了
是不是很疑惑为什么?
当查询区间的时候, 如果在后面的区间内查询到次数不为0时, 说明有几个比他大数在他前面出现过,
重点就是标记。
这里还可以用树状数组来代替线段树。树状数组的特点就是好写,并且速度比线段树快一点。
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
#define Fopen freopen("_in.txt","r",stdin); freopen("_out.txt","w",stdout);
#define LL long long
#define ULL unsigned LL
#define fi first
#define se second
#define pb push_back
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define max3(a,b,c) max(a,max(b,c))
#define min3(a,b,c) min(a,min(b,c))amespace std;
const int N = +;
int tree[N], cnt = , A[N];
pair<int, int> P[N];
int lowbit(int x){
return x&(-x);
}
void Add(int x) {
while(x <= cnt) {
tree[x]++;
x += lowbit(x);
}
}
int Query(int x) {
int ret = ;
while(x) {
ret += tree[x];
x -= lowbit(x);
}
return ret;
}
int main(){
int n;
while(~scanf("%d", &n), n) {
cnt = ;
for(int i = ; i <= n; i++) {
scanf("%d", &A[i]);
}
memset(tree, , sizeof(tree));
LL ans = ;
for(int i = ; i <= n; i++) {
ans += Query(cnt) - Query(A[i]);
Add(A[i]);
}
printf("%I64d\n", ans);
}
return ;
}
注意的是 线段树与树状数组求逆序对的时候 数值不能太大 比如在 a[i] <= 1e9的时候 就不能直接用树状数组和逆序数去求了,因为开不了那么大的空间。
但在这个时候,如果n不是很大可以先对数据进行离散化再进行使用树状数组或者线段树处理数据。
题意就是求逆序对, 但是这个a[i]的范围太大,所以我们不能直接进行求解,但是这个地方只于数的相对大小有关,对于数的差值无关,所以我们可以先对所有数离散化,再进行上述的操作。
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
#define Fopen freopen("_in.txt","r",stdin); freopen("_out.txt","w",stdout);
#define LL long long
#define ULL unsigned LL
#define fi first
#define se second
#define pb push_back
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define max3(a,b,c) max(a,max(b,c))
#define min3(a,b,c) min(a,min(b,c))amespace std;
const int N = +;
int tree[N], cnt = , A[N];
pair<int, int> P[N];
int lowbit(int x){
return x&(-x);
}
void Add(int x) {
while(x <= cnt) {
tree[x]++;
x += lowbit(x);
}
}
int Query(int x) {
int ret = ;
while(x) {
ret += tree[x];
x -= lowbit(x);
}
return ret;
}
int main(){
int n;
while(~scanf("%d", &n), n) {
cnt = ;
for(int i = ; i <= n; i++) {
scanf("%d", &A[i]);
P[i].fi = A[i];
P[i].se = i;
}
sort(P+, P++n);
P[].fi = -;
for(int i = ; i <= n; i++) {
if(P[i-].fi == P[i].fi)
A[P[i].se] = cnt;
else cnt++, A[P[i].se] = cnt;
}
memset(tree, , sizeof(tree));
LL ans = ;
for(int i = ; i <= n; i++) {
ans += Query(cnt) - Query(A[i]);
Add(A[i]);
}
printf("%I64d\n", ans);
}
return ;
}
POJ-2299
Emmm, 上面的都是去年写的,最近突然发现写逆序对不需要那么麻烦, 所以我就再新加一个做法,本来是想删掉的,后来想想各有优点,并且,上面那个博客是最第一个写的这么详细的博客,本来是当时自己太捞了,写给自己看的,2333。
重温一下逆序对的定义, 就是对于每一个数都求出在他前面有多少个数比他大,然后对每一个数的这个东西求和,最后的值就是逆序对数了。
第二种做法就是:讲元素从大到小sort,如果相同的话,位置大的排前面,然后按照这个顺序查询前面的位置有多少个位置的数出现过,然后加到答案里, 然后再标记一下这个位置,以便下次询问。
由于我们是向前询问多少个数出现过了, 并且逆序对的定义是不包括相等的数的,所以我们要前处理位置再后面的数,再处理在前面的数。
原理就是,先出现的数必然大,所以在你前面标记过位置的数就一定大于当前的数。
还是上面那个题目,这个时候我们就不需要离散化了。
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define Fopen freopen("_in.txt","r",stdin); freopen("_out.txt","w",stdout);
#define LL long long
#define ULL unsigned LL
#define fi first
#define se second
#define pb push_back
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define max3(a,b,c) max(a,max(b,c))
#define min3(a,b,c) min(a,min(b,c))amespace std;
const int N = +;
int tree[N], n;
struct Node{
int a;
int id;
}A[N];
bool cmp(Node x1, Node x2){
if(x1.a == x2.a) return x1.id > x2.id;
return x1.a > x2.a;
}
int lowbit(int x){
return x&(-x);
}
void Add(int x) {
while(x <= n) {
tree[x]++;
x += lowbit(x);
}
}
int Query(int x) {
int ret = ;
while(x) {
ret += tree[x];
x -= lowbit(x);
}
return ret;
}
int main(){
while(~scanf("%d", &n), n) {
for(int i = ; i <= n; i++) {
scanf("%d", &A[i].a);
A[i].id = i;
}
memset(tree, , sizeof(tree));
LL ans = ;
sort(A+, A++n, cmp);
for(int i = ; i <= n; i++) {
ans += Query(A[i].id);
Add(A[i].id);
}
printf("%I64d\n", ans);
}
return ;
}
POJ-2299(2)
结论是2个方法都有优点吧, 如果相同元素的个数多的话,离散化或者排序之后n的个数很小,那么前面那种或许会更优, 如果n不大,a[i]的值很大,我们就可以用第二种写法去解决。
当然有些题目只能第二种写法求解如:
二维树状数组的逆序数。 往里拐一下。 传送门。
逆序对 线段树&树状数组 (重制版)的更多相关文章
- BZOJ_3295_[Cqoi2011]动态逆序对_CDQ分治+树状数组
BZOJ_3295_[Cqoi2011]动态逆序对_CDQ分治+树状数组 Description 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一 ...
- [BZOJ3295][Cqoi2011]动态逆序对 CDQ分治&树套树
3295: [Cqoi2011]动态逆序对 Time Limit: 10 Sec Memory Limit: 128 MB Description 对于序列A,它的逆序对数定义为满足i<j,且 ...
- HDU 6318.Swaps and Inversions-求逆序对-线段树 or 归并排序 or 离散化+树状数组 (2018 Multi-University Training Contest 2 1010)
6318.Swaps and Inversions 这个题就是找逆序对,然后逆序对数*min(x,y)就可以了. 官方题解:注意到逆序对=交换相邻需要交换的次数,那么输出 逆序对个数 即可. 求逆序对 ...
- 求逆序对 ----归并排 & 树状数组
网上看了一些归并排求逆序对的文章,又看了一些树状数组的,觉得自己也写一篇试试看吧,然后本文大体也就讲个思路(没有例题),但是还是会有个程序框架的 好了下面是正文 归并排求逆序对 树状数组求逆序对 一. ...
- bzoj3295: [Cqoi2011]动态逆序对(cdq分治+树状数组)
3295: [Cqoi2011]动态逆序对 题目:传送门 题解: 刚学完cdq分治,想起来之前有一道是树套树的题目可以用cdq分治来做...尝试一波 还是太弱了...想到了要做两次cdq...然后伏地 ...
- codevs 4163 求逆序对的数目 -树状数组法
4163 hzwer与逆序对 时间限制: 10 s 空间限制: 256000 KB 题目等级 : 黄金 Gold 题目描述 Description hzwer在研究逆序对. 对于数列{a},如果 ...
- 2019.01.22 bzoj3333: 排队计划(逆序对+线段树)
传送门 题意简述:给出一个序列,支持把ppp~nnn中所有小于等于apa_pap的'扯出来排序之后再放回去,要求动态维护全局逆序对. 思路:我们令fif_ifi表示第iii个位置之后比它大的数的个 ...
- 【bzoj3295】[Cqoi2011]动态逆序对 线段树套SBT
题目描述 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序 ...
- 【a703】求逆序对(线段树的解法)
Time Limit: 10 second Memory Limit: 2 MB 问题描述 给定一个序列a1,a2...an.如果存在i小于j 并且ai大于aj,那么我们称之为逆序对,求给定序列中逆序 ...
随机推荐
- Bean Validation完结篇:你必须关注的边边角角(约束级联、自定义约束、自定义校验器、国际化失败消息...)
每篇一句 没有任何技术方案会是一种银弹,任何东西都是有利弊的 相关阅读 [小家Java]深入了解数据校验:Java Bean Validation 2.0(JSR303.JSR349.JSR380)H ...
- 【Python-Django后端】用户注册通用逻辑,用户名、手机号重名检测,注册成功后状态保持!!!
用户注册后端逻辑 1. 接收参数 username = request.POST.get('username') password = request.POST.get('password') pas ...
- RabbitMQ(四):使用Docker构建RabbitMQ高可用负载均衡集群
本文使用Docker搭建RabbitMQ集群,然后使用HAProxy做负载均衡,最后使用KeepAlived实现集群高可用,从而搭建起来一个完成了RabbitMQ高可用负载均衡集群.受限于自身条件,本 ...
- JAVA基础——Switch条件语句
JAVA基础——switch 条件语句 switch语句结构: switch(表达式){ case值1: 语句体1: break: case值2: 语句体2: break: case值3: 语句体3: ...
- linux环境下搭建自动化Jenkins管理工具
一.搭建一个jak--tomcat服务器到自己的linux服务器上 具体的服务器搭建这里可以参考华华大佬的博客:https://www.cnblogs.com/liulinghua90/p/46614 ...
- 【游记】NOIP2019初赛
声明 我的游记是一个完整的体系,如果没有阅读过往届文章,阅读可能会受到障碍. ~~~上一篇游记的传送门~~~ 前言 (编辑中) 文章推荐:[游记]NOIP2019复赛
- 使用excel计算骰子输赢概率
如何得到使用3个骰子掷赢4个骰子的概率(每个骰子的点数为1-6,点数一样算输) 分为3步解决: 第一步.计算n个骰子得到m点数的分布 1个骰子能得到1.2.3.4.5.6点数,每个点数出现的方式只有1 ...
- HTML/CSS:图片居中(水平居中和垂直居中)
css图片居中(水平居中和垂直居中) css图片居中分css图片水平居中和垂直居中两种情况,有时候还需要图片同时水平垂直居中, 下面分几种居中情况分别介绍: css图片水平居中 1.利用margin: ...
- Linux常用命令之ftp
FTP是Internet用户使用最频繁的文件上传.下载的命令之一.linux ftp用命令的方式来控制在本机和远程ftp服务器之间传送文件.ftp中的命令包括上传文件(单个.多个),下载文件(单个.多 ...
- 使用IDEA打包scala程序并在spark中运行
一.首先配置ssh无秘钥登陆, 先使用这条命令:ssh-keygen,然后敲三下回车: 然后使用cd .ssh进入 .ssh这个隐藏文件夹: 再创建一个文件夹authorized_keys,使用命令t ...