树状数组求逆序对:POJ 2299、3067

  前几天开始看树状数组了,然后开始找题来刷。

  首先是 POJ 2299 Ultra-QuickSort: http://poj.org/problem?id=2299

  这题是指给你一个无序序列,只能交换相邻的两数使它有序,要你求出交换的次数。实质上就是求逆序对,网上有很多人说它的原理是冒泡排序,可以用归并排序来求出,但我一时间想不出它是如何和归并排序搭上边的(当初排序没学好啊~),只好用刚学过的树状数组来解决了。在POJ 1990中学到了如何在实际中应用上树状数组,没错,就是用个特殊的数组来记录即可,然后区间查询和单点更新的复杂度都在O(logn)范围内。

  对于这道题,即处理到第i个数时,可以通过计算 i-1-它前面比它小的数得出前面比它大的数,即该数前面的逆序数;而统计某数前面比它小的数就是树状数组的裸题了。分析到这里已经差不多了,但是一看,a[i] ≤ 999,999,999,就不能简单地用数组下标来记录a[i]的值了,这时我想起了之前看过的一个很高大上的名字:"离散化",没错,就是离散化,因为n < 500,000而已,用不着那么多数组的空间,把a[i]通过排序再放进数组即可,不过具体的题我还没做过,对于这道题,我的做法是设置一个辅助数组 r[i] 表示第i个数的下标,初始化时也就是r[i]= i,然后通过比较对应a[i]的a[j]的值对r[]排序。说白了就是对数组a[]的下标进行排序(利用了刘汝佳小白书上kruskal的做法),这时候数组r[]的逆序数就是数组a[]的逆序数。这是因为在排序时是一一对应的,每交换两个数使a[]消除一个逆序对时r[]便增加一个逆序对(r[]本来是1,2,3,4,5……的顺序数列),所以就转化为了求数组r[]的逆序对,此时下标只受n<500,000约束,开数组不成问题,详见代码:

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 typedef long long LL;
 6 const LL maxn= 500008;
 7
 8 inline int lowbit(int x) {    return x&(-x);    }
 9
10 struct treeArray{
11     int c[maxn], n;
12     treeArray(int n=0):n(n)    {    memset(c,0,sizeof(c));    }
13     void clear() {    memset(c,0,sizeof(c));    }
14     int sum(int x) const {
15         int ans= 0;
16         while(x){
17             ans+= c[x];
18             x-= lowbit(x);
19         }
20         return ans;
21     }
22     void add(int x, int d){
23         while(x<=n){
24             c[x]+= d;
25             x+= lowbit(x);
26         }
27     }
28 } num(maxn);
29
30 int a[maxn], r[maxn];
31 bool cmp(int i, int j)    {    return a[i]<a[j];    }
32
33 int main(){
34     int n,i;
35     while(~scanf("%d",&n),n){
36         for(i=1; i<=n; ++i){
37             scanf("%d",a+i);
38             r[i]= i;
39         }
40         sort(r+1,r+n+1,cmp);
41         LL inv= 0;
42         num.clear();
43         num.n= n;
44         for(i=1; i<=n; ++i){
45             inv+= i-1-num.sum(r[i]);
46             num.add(r[i],1);
47         }
48         printf("%lld\n",inv);
49     }
50     return 0;
51 }

  树状数组的写法我参照了网上的人的写法,把它封装成一个结构体,用于实现区间查询和单点更新的数组c[]放在了结构体内,这样子可使代码更清晰,不至于要开个全局数组容易混淆,如同矩阵快速幂一样把矩阵的乘法重载在结构体中,可以避免在一些细节上浪费精力,代码量也没有增加多少。

  然后是第二道 POJ 3067: http://poj.org/problem?id=3067

  这题主要是说东西两边各有1~N和1~M个城市,然后有K条道路连接东西两边的城市,要求这K条边的所有交点。这里有个不大却很易栽的坑:K的范围没给出,所以可以猜想其为N*M的上限,然后它给出的道路也没说是有序的(千万别被样例骗了),所以我们首先要对它进行预处理才行。

  如上题一样,可以利用求逆序对的做法来求。为什么呢?我们可以先按e(就是左边的坐标)排升序,e相同的话按w(右边的坐标)排升序(w一定要为升序,因为e相同时的边是不会有交叉点的,所以w升序在统计时可以使e相同而w不同的边不存在逆序对,符合没有交叉点的实际情况),大体的预处理就是这样,然后对排好序的w边进行求逆序数即可,具体求法如上题所述。和上题不同的是,这里的w值可以和前面的重复,但也不影响树状数组的运用,要处理好细节,记住先返回区间的值即sum(x),再去更新即add(x)(数组c[x]在add(x)前为0,不影响sum(x)的统计)。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 typedef long long LL;
 6 #define lowbit(x) ((x)&-(x))
 7 const int maxn= 1000006;
 8
 9 struct treeArray{
10     int c[maxn], n;
11     treeArray(int n=0):n(n)    {    memset(c,0,sizeof(c));    }
12     void clear() {    memset(c,0,sizeof(c));    }
13     LL sum(int x) const {
14         LL ans= 0;
15         while(x){
16             ans+= c[x];
17             x-= lowbit(x);
18         }
19         return ans;
20     }
21     void add(int x, int d){
22         while(x<=n) {        //这里是 wa的根源?
23             c[x]+= d;
24             x+= lowbit(x);
25         }
26     }
27 } num(maxn);
28
29 struct Edge{
30     int e,w;
31     bool operator <(const Edge E2) const {
32         if(e==E2.e)      return w<E2.w;
33         return e<E2.e;
34     }
35 } edge[maxn];
36
37 void solve(){
38     static int p=0;
39     int n,m,k;
40     scanf("%d%d%d",&n,&m,&k);
41     for(int i=1; i<=k; ++i)
42         scanf("%d%d",&edge[i].e, &edge[i].w);
43
44     sort(edge+1,edge+k+1);
45     LL inv= 0;
46     num.clear();
47     num.n= m;        //原来这里才是 wa的根源!!
48     for(int i=1; i<=k; ++i){
49         inv+= i-1-num.sum(edge[i].w);
50         num.add(edge[i].w,1);
51     }
52     printf("Test case %d: %lld\n",++p,inv);
53 }
54
55 int main(){
56     int t;
57     scanf("%d",&t);
58     while(t--)    solve();
59     return 0;
60 }

  奇怪的是,这题我wa了近10遍才过(T.T),一开始怎么也找不着错的原因,各种long long、__int64、%lld、%I64d的输入输出姿势都试过了,还是wa,没办法,只好和标程一点一点地对拍,到最后才发现真正的错误是在num.n= m这里,就是没处理好树状数组c[]的n,这种细节问题还是第一次见,不过也需记住这个错误,因为树状数组的结构体模板以后应该会经常用到,还是得多刷题了,昨晚竟被老师和师兄说自己的进度太慢,听后除了惭愧外还有一丝恨铁不成钢(我对自己)的心理,大白书,Come on!

时间: 2024-12-25 15:00:11

树状数组求逆序对:POJ 2299、3067的相关文章

(离散化+树状数组求逆序数) poj 2299

Ultra-QuickSort Time Limit: 7000MS   Memory Limit: 65536K Total Submissions: 44390   Accepted: 16149 Description In this problem, you have to analyze a particular sorting algorithm. The algorithm processes a sequence of n distinct integers by swappin

【bzoj2789】[Poi2012]Letters 树状数组求逆序对

题目描述 给出两个长度相同且由大写英文字母组成的字符串A.B,保证A和B中每种字母出现的次数相同. 现在每次可以交换A中相邻两个字符,求最少需要交换多少次可以使得A变成B. 输入 第一行一个正整数n (2<=n<=1,000,000),表示字符串的长度. 第二行和第三行各一个长度为n的字符串,并且只包含大写英文字母. 输出 一个非负整数,表示最少的交换次数. 样例输入 3 ABC BCA 样例输出 2 题解 树状数组求逆序对 一个结论:将序列A通过交换相邻元素变换为序列B,需要的最小次数为A中

Day2:T4用树状数组求逆序对

T4: 用树状数组求逆序对 A[I]为前缀和 推导 (A[J]-A[I])/(J-I)>=M A[j]-A[I]>=M(J-I) A[J]-M*J>=A[I]-M*I B[J]>=B[I] 之后就是求逆序对的事情了 然后这里学一下用树状数组的方法 原理是:树状数组是用来求区间和的是吧 就是按权值的区间统计那么可以BIT维护...然后扫一遍 也就是计算有多少个逆序对 按权值的区间统计就是记录数的个数

蓝桥杯小朋友排队(树状数组求逆序对)

居然存在身高为0的数据... 树状数组求逆序对原理: add(h[j],1); //将身高为h[j]的数据的出现次数加1 sum(h[j]);//求i<j 且 h[i] <=h[j] 的数据出现次数之和  那么 i-sum(h[j]) 为 i > j 且 h[i] > h[j] 数据的出现次数之和 即为 逆序对数 #include"cstdio" #include"cstring" #define lowbit(i) i&(-i) u

树状数组求逆序对

给定n个数,要求这些数构成的逆序对的个数.除了用归并排序来求逆序对个数,还可以使用树状数组来求解.树状数组求解的思路:开一个能大小为这些数的最大值的树状数组,并全部置0.从头到尾读入这些数,每读入一个数就更新树状数组,查看它前面比它小的已出现过的有多少个数sum,然后用当前位置减去该sum,就可以得到当前数导致的逆序对数了.把所有的加起来就是总的逆序对数.题目中的数都是独一无二的,这些数最大值不超过999999999,但n最大只是500000.如果采用上面的思想,必然会导致空间的巨大浪费,而且由

hdu5792 World is Exploding(多校第五场)树状数组求逆序对 离散化

题目地址:http://acm.hdu.edu.cn/showproblem.php?pid=5792 题目描述:给你n个值,每个值用A[i]表示,然后问你能否找到多少组(a,b,c,d)四个编号,四个编号互不相同,然后a < b, c < d,a代表的值小于b代表的值,c代表的值大于d代表的值. 解题思路:先考虑a和b这两个编号,遍历每一个编号作为b,然后找到b前面有多少个小于b的值,就是对于这一个编号b合理的编号a,对于每一组a和b,就可以考虑c和d,能够满足条件c和d的很显然就是除去a和

hdu1394Minimum Inversion Number树状数组求逆序对水题

//ans[i]=ans[i-1]+(n+1)-2*num[i] //num[i]为输入时的数据 //ans[i]为m=i时的逆序数 //用树状数组求ans[0]的逆序对 #include<iostream> #include<cstdio> #include<cstring> using namespace std; const int maxn=5010; int num[maxn]; int tree[maxn]; int lowbit(int i) { retu

hdu5147 Sequence II树状数组求逆序对

//用树状数组求出在b前面比b小的数的个数 //然后求b后面的顺序对的个数, //枚举b可得quad //由于数列是从1到n的所有数 //那么(n-num[j])-(j-1-totol[j])即为第j个数之后比j大的数的个数 //其中num[j]表示第j个数,total[j]表示在j之前比j小的数的个数 #include<iostream> #include<cstdio> #include<cstring> using namespace std; const int

HDU 1394 Minimum Inversion Number (树状数组求逆序对)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1394 题目让你求一个数组,这个数组可以不断把最前面的元素移到最后,让你求其中某个数组中的逆序对最小是多少. 一开始就求原来初始数组的逆序对,树状数组求或者归并方法求(可以看<挑战程序设计>P178),然后根据最前面的元素大小递推一下每次移到最后得到的逆序数,取最小值. 1 #include <iostream> 2 #include <cstdio> 3 #include

loj #535. 「LibreOJ Round #6」花火 树状数组求逆序对+主席树二维数点+整体二分

$ \color{#0066ff}{ 题目描述 }$ 「Hanabi, hanabi--」 一听说祭典上没有烟火,Karen 一脸沮丧. 「有的哦-- 虽然比不上大型烟花就是了.」 还好 Shinobu 早有准备,Alice.Ayaya.Karen.Shinobu.Yoko 五人又能继续愉快地玩耍啦! 「噢--!不是有放上天的烟花嘛!」Karen 兴奋地喊道. 「啊等等--」Yoko 惊呼.Karen 手持点燃引信的烟花,「嗯??」 Yoko 最希望见到的是排列优美的烟火,当然不会放过这个机会-