树状树状与逆序对

众所周知,树状数组是一种十分神奇的数据结构,利用它我们可以解决一系列和逆序对相关的问题。

P1908 逆序对

输入输出格式

输入格式:

第一行,一个数n,表示序列中有n个数。

第二行n个数,表示给定的序列。序列中每个数字不超过10^9

 

输出格式:

给定序列中逆序对的数目。

输入输出样例

输入样例#1: 
6
5 4 2 6 3 1
输出样例#1: 
11

说明

对于25%的数据,n2500

对于50%的数据,n4×1^4。

对于所有数据,n5×1^5

 

这是一道模板题,这里说一下树状数组如何计算逆序对数,首先我们知道,在一个序列中,逆序对数就等于每一个元素的右边比它小的元素个数之和,这样我们就可以用树状数组来统计每一个数右边比它大的元的个数了。首先我们把原序列扫描一遍,每遇到一个数a[i]就把这个数a[i]在树状数组中的位置上加上1,然后查询a[i]+1到n的区间和,并累加进入答案。

当然这个方法只适用于原序列的数值非常小的情况,那么如果原序列的数值不能直接当做数组的下标,这该怎么办呢?离散化。首先我们将原序列进行离散化,由于我们只需要知道元素之间的相对大小,而不需要确切知道元素的具体值,这样我们就可以将元素在序列中的大小排名看做它的相对大小,这样不就实现离散化了吗。由于不需要去重,所以只需要两个函数就可以实现。

1     sort(a+1,a+1+n);//sort用来排序 
2     for(int i=1;i<=n;i++)
3     {
4         b[i]=lower_bound(a+1,a+1+n,b[i])-a;//lower_bound返回在a数    组中第一个大于等于b[i]元素的指针,这里减去a就是b[i]在a数组中的排名 
5     }
View Code

实现了离散化问题就可以用树状数组解决了。

 1 #include<iostream>
 2 #include<string>
 3 #include<cmath>
 4 #include<algorithm>
 5 #include<cstring>
 6 #include<cstdio>
 7 #include<stack>
 8 #include<queue>
 9 #include<vector>
10 #define maxn 500005
11 
12 using namespace std;
13 
14 inline int read()
15 {
16     int x=1,res=0;
17     char c=getchar();
18     while(c<'0'||c>'9')
19     {
20         if(c=='-')
21         x=-1;
22         c=getchar();
23     }
24     while(c>='0'&&c<='9')
25     {
26         res=res*10+(c-'0');
27         c=getchar();
28     }
29     return x*res;
30 }
31 
32 long long n,aa;
33 long long a[maxn],b[maxn];
34 long long c[maxn],ans;
35 
36 long long low(int x)
37 {
38     return x&(-x);
39 }
40 
41 void add(long long x)
42 {
43     for(long long i=x;i<=n;i+=low(i))
44     {
45         c[i]++;
46     }
47 }
48 
49 long long ask(long long x)
50 {
51     long long sum=0;
52     for(long long i=x;i>0;i-=low(i))
53     {
54         sum+=c[i];
55     }
56     return sum;
57 }
58 
59 int main()
60 {
61     scanf("%lld",&n);
62     for(int i=1;i<=n;i++)
63     {
64         scanf("%lld",&aa);
65         b[i]=a[i]=aa;
66     }
67     sort(a+1,a+1+n);//sort用来排序 
68     for(int i=1;i<=n;i++)
69     {
70         b[i]=lower_bound(a+1,a+1+n,b[i])-a;//lower_bound返回在a数组中第一个大于等于b[i]元素的指针 
71         //这里减去a就是b[i]在a数组中的排名 
72     }
73     for(int i=1;i<=n;i++)
74     {
75         add(b[i]);
76         ans+=ask(n)-ask(b[i]);
77     }
78     printf("%lld\n",ans);
79     return 0;
80 }
View Code

 

下面有几道十分有趣的题目:

P3608 [USACO17JAN]Balanced Photo

题目描述

FJ正在安排他的N头奶牛站成一排来拍照。(1<=N<=100,000)序列中的第i头奶牛的高度是h[i],且序列中所有的奶牛的身高都不同。

就像他的所有牛的照片一样,FJ希望这张照片看上去尽可能好。他认为,如果L[i]和R[i]的数目相差2倍以上的话,第i头奶牛就是不平衡的。(L[i]和R[i]分别代表第i头奶牛左右两边比她高的数量)。如果L[i]和R[i]中较大者比较小者的数量严格多两倍的话,这头奶牛也是不平衡的。FJ不希望他有太多的奶牛不平衡。

请帮助FJ计算不平衡的奶牛数量。

输入输出格式

输入格式:

第一行一个整数N。接下N行包括H[1]到H[n],每行一个非负整数(不大于1,000,000,000)。

 

输出格式:

请输出不平衡的奶牛数量。

 

输入输出样例

输入样例#1: 
7
34
6
23
0
5
99
2
输出样例#1: 
3

我们可以类比计算逆序对的方法,首先对数据进行离散化,然后统计每一个数的左边和右边比它大的元素个数,然后从1扫到n,统计 (l[i]*2<r[i])or(l[i]>2*r[i])的元素个数。

  1 #include<iostream>
  2 #include<string>
  3 #include<cstdio>
  4 #include<cmath>
  5 #include<cstring>
  6 #include<map>
  7 #include<algorithm>
  8 #include<stack>
  9 #include<queue>
 10 #include<vector>
 11 #define maxn 100005
 12 using namespace std;
 13 
 14 inline int read()
 15 {
 16     int x=1,res=0;
 17     char c=getchar();
 18     while(c<'0'||c>'9')
 19     {
 20         if(c=='-')
 21         x=-1;
 22         c=getchar();
 23     }
 24     while(c>='0'&&c<='9')
 25     {
 26         res=res*10+(c-'0');
 27         c=getchar();
 28     }
 29     return res*x;
 30 }
 31 
 32 int n,aa,ans;
 33 int a[maxn],b[maxn];
 34 long long c1[maxn],c2[maxn],r[maxn],l[maxn];
 35 
 36 int low(int x)
 37 {
 38     return x&(-x);
 39 }
 40 
 41 void add1(int x)
 42 {
 43     for(int i=x;i<=n;i+=low(i))
 44     {
 45         c1[i]++;
 46     }
 47 }
 48 
 49 long long ask1(int x)
 50 {
 51     long long ans=0;
 52     for(int i=x;i>0;i-=low(i))
 53     {
 54         ans+=c1[i];
 55     }
 56     return ans;
 57 }
 58 
 59 void add2(int x)
 60 {
 61     for(int i=x;i<=n;i+=low(i))
 62     {
 63         c2[i]++;
 64     }
 65 }
 66 
 67 long long ask2(int x)
 68 {
 69     long long ans=0;
 70     for(int i=x;i>0;i-=low(i))
 71     {
 72         ans+=c2[i];
 73     }
 74     return ans;
 75 }
 76 
 77 int main()
 78 {
 79     n=read();
 80     for(register int i=1;i<=n;i++)
 81     {
 82         aa=read();
 83         a[i]=b[i]=aa;
 84     }
 85     sort(a+1,a+1+n);
 86     for(register int i=1;i<=n;i++)
 87     {
 88         b[i]=lower_bound(a+1,a+1+n,b[i])-a;//由于数据很大需要离散化 
 89     }
 90     for(register int i=1;i<=n;i++)
 91     {
 92         add1(b[i]);
 93         l[i]=ask1(n)-ask1(b[i]);//计算每个数左边有多少个数大于它 
 94     }
 95     for(register int i=n;i>=1;i--)
 96     {
 97         add2(b[i]);
 98         r[i]=ask2(n)-ask2(b[i]);//计算每个数右边有多少个数大于它
 99     }
100     for(register int i=1;i<=n;i++)
101     {
102         if(l[i]*2<r[i]||l[i]>r[i]*2) 
103         ans++;
104     }
105     printf("%d\n",ans);
106     return 0;
107 }
View Code

 

P1637 三元上升子序列

题目描述

Erwin最近对一种叫"thair"的东西巨感兴趣。。。

在含有n个整数的序列a1,a2......an中,

三个数被称作"thair"当且仅当i<j<k且ai<aj<ak

求一个序列中"thair"的个数。

输入输出格式

输入格式:

开始一个正整数n,

以后n个数a1~an。

 

输出格式:

"thair"的个数

 

输入输出样例

输入样例#1: 
4
2 1 3 4
输出样例#1: 
2
输入样例#2: 
1 2 2 3 4
输出样例#2: 
7
和上一道题几乎一样,对于序列中的每一个数,统计一下左边有多少个数小于它,以及右边有多少个数大于它,乘一下再求和就好了。
  1 #include<iostream>
  2 #include<string>
  3 #include<cstdio>
  4 #include<cmath>
  5 #include<cstring>
  6 #include<map>
  7 #include<algorithm>
  8 #include<stack>
  9 #include<queue>
 10 #include<vector>
 11 #define maxn 30005
 12 using namespace std;
 13 
 14 inline int read()
 15 {
 16     int x=1,res=0;
 17     char c=getchar();
 18     while(c<'0'||c>'9')
 19     {
 20         if(c=='-')
 21         x=-1;
 22         c=getchar();
 23     }
 24     while(c>='0'&&c<='9')
 25     {
 26         res=res*10+(c-'0');
 27         c=getchar();
 28     }
 29     return res*x;
 30 }
 31 
 32 int T,n;
 33 int a[maxn],b[maxn];
 34 long long c1[maxn],c2[maxn];
 35 long long d1[maxn],d2[maxn];
 36 long long ans;
 37 
 38 int low(int x)
 39 {
 40     return x&(-x);
 41 }
 42 
 43 void add1(int x,int y)
 44 {
 45     for(int i=x;i<=n;i+=low(i))
 46     {
 47         c1[i]+=y;
 48     }
 49 }
 50 
 51 long long ask1(int x)
 52 {
 53     long long ans=0;
 54     for(int i=x;i>0;i-=low(i))
 55     {
 56         ans+=c1[i];
 57     }
 58     return ans;
 59 }
 60 
 61 void add2(int x,int y)
 62 {
 63     for(int i=x;i<=n;i+=low(i))
 64     {
 65         c2[i]+=y;
 66     }
 67 }
 68 
 69 long long ask2(int x)
 70 {
 71     long long ans=0;
 72     for(int i=x;i>0;i-=low(i))
 73     {
 74         ans+=c2[i];
 75     }
 76     return ans;
 77 }
 78 
 79 int main()
 80 {
 81     ans=0;
 82     n=read();
 83     for(int i=1;i<=n;i++)
 84     {
 85         a[i]=read();
 86         b[i]=a[i];
 87     }
 88     sort(a+1,a+1+n);
 89     for(int i=1;i<=n;i++)
 90     {
 91         b[i]=lower_bound(a+1,a+1+n,b[i])-a;
 92     }
 93     for(int i=1;i<=n;i++)
 94     {
 95         add1(b[i],1);
 96         d1[i]=ask1(b[i]-1);
 97     }
 98     for(int i=n;i>=1;i--)
 99     {            
100         add2(b[i],1);
101         d2[i]=ask2(n)-ask2(b[i]);
102     }
103     for(int i=1;i<=n;i++)
104     {    
105         ans+=d1[i]*d2[i];
106     }
107     printf("%lld\n",ans);
108     return 0;
109 }
View Code

 


UVA1428 Ping pong

一条大街上住着n个乒乓球爱好者,经常组织比赛切磋技术。每个人都有一个不同的技能值ai。每场比赛需要3个人:两名选手,一名裁判。他们有一个奇怪的规定,即裁判住在两名选手中间,并且技能值也在两名选手之间。问一共能组织多少种比赛

说白了这道题和上一题一模一样,只不过正着求一次再反着求一次就可以了。

  1 #include<iostream>
  2 #include<string>
  3 #include<cstdio>
  4 #include<cmath>
  5 #include<cstring>
  6 #include<map>
  7 #include<algorithm>
  8 #include<stack>
  9 #include<queue>
 10 #include<vector>
 11 #define maxn 20005
 12 using namespace std;
 13 
 14 inline int read()
 15 {
 16     int x=1,res=0;
 17     char c=getchar();
 18     while(c<'0'||c>'9')
 19     {
 20         if(c=='-')
 21         x=-1;
 22         c=getchar();
 23     }
 24     while(c>='0'&&c<='9')
 25     {
 26         res=res*10+(c-'0');
 27         c=getchar();
 28     }
 29     return res*x;
 30 }
 31 
 32 int T,n;
 33 int a[maxn],b[maxn];
 34 long long c1[maxn],c2[maxn];
 35 long long d1[maxn],d2[maxn],d3[maxn],d4[maxn];
 36 long long ans;
 37 
 38 int low(int x)
 39 {
 40     return x&(-x);
 41 }
 42 
 43 void add1(int x,int y)
 44 {
 45     for(int i=x;i<=n;i+=low(i))
 46     {
 47         c1[i]+=y;
 48     }
 49 }
 50 
 51 long long ask1(int x)
 52 {
 53     long long ans=0;
 54     for(int i=x;i>0;i-=low(i))
 55     {
 56         ans+=c1[i];
 57     }
 58     return ans;
 59 }
 60 
 61 void add2(int x,int y)
 62 {
 63     for(int i=x;i<=n;i+=low(i))
 64     {
 65         c2[i]+=y;
 66     }
 67 }
 68 
 69 long long ask2(int x)
 70 {
 71     long long ans=0;
 72     for(int i=x;i>0;i-=low(i))
 73     {
 74         ans+=c2[i];
 75     }
 76     return ans;
 77 }
 78 
 79 int main()
 80 {
 81     T=read();
 82     while(T--)
 83     {
 84         memset(c1,0,sizeof(c1));
 85         memset(c2,0,sizeof(c2));
 86         ans=0;
 87         n=read();
 88         for(int i=1;i<=n;i++)
 89         {
 90             a[i]=read();
 91             b[i]=a[i];
 92         }
 93         sort(a+1,a+1+n);
 94         for(int i=1;i<=n;i++)
 95         {
 96             b[i]=lower_bound(a+1,a+1+n,b[i])-a;
 97         }
 98         for(int i=1;i<=n;i++)
 99         {
100             add1(b[i],1);
101             d1[i]=ask1(b[i]-1);
102             d3[i]=ask1(n)-ask1(b[i]);
103         }
104         for(int i=n;i>=1;i--)
105         {
106             add2(b[i],1);
107             d2[i]=ask2(n)-ask2(b[i]);
108             d4[i]=ask2(b[i]-1);
109         }
110         for(int i=1;i<=n;i++)
111         {
112             ans+=d1[i]*d2[i]+d3[i]*d4[i];
113         }
114         printf("%lld\n",ans);
115     }
116     return 0;
117 }
View Code

 

posted @ 2019-02-16 15:38  snowy2002  阅读(272)  评论(0编辑  收藏  举报