逆序对 线段树&树状数组 (重制版)
逆序对的定义:长度为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,那么我们称之为逆序对,求给定序列中逆序 ...
随机推荐
- oracle一条语句插入多个值的方法
今天在实践过程中遇到一个问题, 我想往数据库插入多条数据时,使用了如下语句: insert into 表1 (字段1,字段2) values (1,2),(2,3),(3,4); 这条语句在mysql ...
- SpringBoot RabbitMQ 整合使用
![](http://ww2.sinaimg.cn/large/006tNc79ly1g5jjb62t88j30u00gwdi2.jpg) ### 前提 上次写了篇文章,[<SpringBoot ...
- Oracle DBLink跨数据库访问SQL server数据同步 踩坑实录
项目需求:这里暂且叫A公司吧,A公司有一套人事管理软件,需要与我们公司的软件做人员信息同步,A公司用的是SQL server数据库,我们公司用的Oracle,接口都不会开发(一万句"fuck ...
- 基于vue2.0搭建项目流程
搭建vue2.0项目--myproject 一. 环境搭建: 1 打开命令行(cmd) 2 安装node node官网 3 安装 vue-cli步骤如下: npm install -g vue-cli ...
- Linux 常用命令及详解
1. type :查询命令 是否属于shell解释器2. help : 帮助命令3. man : 为所有用户提供在线帮助4. ls : 列表显示目录内的文件及目录-l 以长格式显 ...
- 前端笔记之React(八)上传&图片裁切
一.上传 formidable天生可以处理上传的文件,非常简单就能持久上传的文件. 今天主要讲解的是,前后端的配合套路. 上传分为同步.异步.同步公司使用非常多,异步我们也会讲解. 1.1 先看一下a ...
- Spring文档学习
Spring文档学习 参考Spring Framework Documentation学习 1. IoC 容器 1.1 容器实例化 <beans> <import resource= ...
- 死磕JVM之类中各部分的加载顺序
话不多说,直接上代码: 1.通过new创建对象实例: 2.当对象中含有静态方法,且调用时: -- 调用父类静态方法: 总结: * 类中静态资源首次加载的时间是类中静态资源第一次被调用的时候或者该类的对 ...
- 2019牛客多校训练第三场B.Crazy Binary String(思维+前缀和)
题目传送门 大致题意: 输入整数n(1<=n<=100000),再输入由n个0或1组成的字符串,求该字符串中满足1和0个数相等的最长子串.子序列. sample input: 801001 ...
- c# 将dwg文件转化为pdf
https://blog.csdn.net/mywaster/article/details/50220379 最近做一个项目,要求将dwg文件转化为pdf,开发工具VS2010 + AutoCad ...