逆序对 线段树&树状数组 (重制版)
逆序对的定义:长度为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,那么我们称之为逆序对,求给定序列中逆序 ...
随机推荐
- PHP 防范xss攻击(转载)
XSS 全称为 Cross Site Scripting,用户在表单中有意或无意输入一些恶意字符,从而破坏页面的表现! 看看常见的恶意字符XSS 输入: 1.XSS 输入通常包含 JavaScript ...
- poj 1050 To the Max(最大子矩阵之和)
http://poj.org/problem?id=1050 我们已经知道求最大子段和的dp算法 参考here 也可参考编程之美有关最大子矩阵和部分. 然后将这个扩大到二维就是这道题.顺便说一下,有 ...
- HashMap、Hash Table、ConcurrentHashMap
这个这个...本王最近由于开始找实习工作了,所以就在牛客网上刷一些公司的面试题,大多都是一些java,前端HTML,js,jquery,以及一些好久没有碰的算法题,说实话,有点难受,其实在我不知道的很 ...
- Redis分布式锁实战
什么是分布式锁 在单机部署的情况下,要想保证特定业务在顺序执行,通过JDK提供的synchronized关键字.Semaphore.ReentrantLock,或者我们也可以基于AQS定制化锁.单机部 ...
- java封装 redis 操作 对象,list集合 ,json串
/** * 功能说明: * 功能作者: * 创建日期: * 版权归属:每特教育|蚂蚁课堂所有 www.itmayiedu.com */package com.redis.service; import ...
- Tomcat源码分析 (五)----- Tomcat 类加载器
在研究tomcat 类加载之前,我们复习一下或者说巩固一下java 默认的类加载器.楼主以前对类加载也是懵懵懂懂,借此机会,也好好复习一下. 楼主翻开了神书<深入理解Java虚拟机>第二版 ...
- hadoop2.7+spark2.2+zookeeper3.4.简单安装
1.zookeeper的安装##配置/etc/hosts192.168.88.130 lgh192.168.88.131 lgh1192.168.88.132 lgh2 ##安装java8 解压配置环 ...
- 开发规范 小白进阶 python代码规范化
开发规范 软件开发,规范项目的目录结构,代码规范,遵循 PeP8规范等等,让你更加清晰的,合理开发 一功能分类(文件名) settings.py配置文件 配置文件放一些静态参数, 划归固定的路径,文件 ...
- testng学习笔记-- 场景和module
一.定义 TestNG是一个测试框架,其灵感来自JUnit和NUnit,但引入了一些新的功能,使其功能更强大,使用更方便. TestNG是一个开源自动化测试框架;TestNG表示下一代(Next Ge ...
- 浅谈UART/12C/TTL的定义与区别与解析
UART/12C/TTL的定义与区别: UART:UART(Universal Asynchronous Receive Transmitter):也就是我们经常所说的串口,基本都用于调试.主机和从机 ...