逆序对 线段树&树状数组 (重制版)
逆序对的定义:长度为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,那么我们称之为逆序对,求给定序列中逆序 ...
随机推荐
- 【Java】Exception thrown by the agent : java.rmi.server.ExportException: Port already in use: 1099
详细信息如下: Error: Exception thrown by the agent : java.rmi.server.ExportException: Port already in use: ...
- 【Android】Fresco 初次使用遇到的坑
初次使用开源框架 Fresco,结果遇到了坑,被虐了半下午--暂且记下. 下面的错误 android.view.InflateException: Binary XML file line #** 报 ...
- 【nodejs原理&源码赏析(9)】用node-ssh实现轻量级自动化部署
目录 一. 需求描述 二. 预备知识 IP+端口访问 域名访问 三. Nodejs应用的手动部署 四. 基于nodejs的自动部署 4.1 package.json中的scripts 4.2 自动化发 ...
- jenkins弱口令漏洞
jenkins弱口令漏洞 一.漏洞描述 通过暴力破解管理控制台,如果爆破成功,可获得后台管理权限.操作后台,后台可通过脚本命令行功能执行系统命令,如反弹shell等,低权限可以通过创建控制台输出方式执 ...
- mysql5.7.18-winx64安装
win10下装mysql-5.7.18-winx64 步骤1 官网下载地址:https://dev.mysql.com/downloads/mysql/ 选择手动安装版: 解压到D盘mysql文件夹下 ...
- Linux 清理空间
背景: 在使用Linux服务器的时候,经常会碰到服务器上的磁盘空间满了,在该种情况下,必须进行磁盘空间清理. 解决方法: 示例:需要/tmp下空出至少1G的可用空间 分别执行的命令如下: df /tm ...
- Cobbler 自动安装CentOS7
1. Cobbler介绍 Cobbler是一个Linux服务器安装的服务,可以通过网络启动(PXE)的方式来快速安装.重装物理服务器和虚拟机,同时还可以管理DHCP,DNS等.Cobbler可以使用命 ...
- kali,ubuntu, debain DNS 配置
kali 是基于 debain 的一个 Linux 发行版 DNS 的配置 是在文件 /etc/resolv.conf 下. 但是,我们会发现 /etc/resolv.conf 每次重启都会失效, ...
- Xamarin Android 监听音量键(下)
上篇在 MainActivity 中重写了按键事件(OnKeyDown),获取了音量键键值并打印了出来,当然,手机物理按键和虚拟按键(音量键.返回键.菜单键等)均可通过该按键事件被捕获. 但是,按键重 ...
- RocketMQ中PullConsumer的启动源码分析
通过DefaultMQPullConsumer作为默认实现,这里的启动过程和Producer很相似,但相比复杂一些 [RocketMQ中Producer的启动源码分析] DefaultMQPullCo ...