题目描述
在完成了分配任务之后,西部314来到了楼兰古城的西部。相传很久以前这片土地上(比楼兰古城还早)生活着两个部落,一个部落崇拜尖刀 ( V ) (V) (V),一个部落崇拜铁锹 ( ∧ ) (∧) (∧),他们分别用 V V V和 ∧ ∧ ∧的形状来代表各自部落的图腾。
西部314在楼兰古城的下面发现了一幅巨大的壁画,壁画上被标记出了 N N N个点,经测量发现这 N N N个点的水平位置和竖直位置是两两不同的。西部314认为这幅壁画所包含的信息与这 N N N个点的相对位置有关,因此不妨设坐标分别为 ( 1 , y 1 ) , ( 2 , y 2 ) , ⋯   , ( n , y n ) , (1,y_1),(2,y_2),\cdots,(n,y_n), (1,y1),(2,y2),⋯,(n,yn),其中 y 1 y_1 y1~ y n y_n yn是 1 1 1到 N N N的一个排列。
西部314打算研究这幅壁画中包含着多少个图腾,其中 V V V图腾的定义如下(注意:图腾的形式只和这三个纵坐标的相对大小排列顺序有关) 1 ≤ i < j < k ≤ n 1\le i<j<k\le n 1≤i<j<k≤n且 y i > y j , y j < y k y_i>y_j,y_j<y_k yi>yj,yj<yk;
而崇拜∧的部落的图腾被定义为 ≤ i < j < k ≤ n \le i<j<k\le n ≤i<j<k≤n且 y i < y j , y j > y k y_i<y_j,y_j>y_k yi<yj,yj>yk;
西部314想知道,这 n n n个点中两个部落图腾的数目。因此,你需要编写一个程序来求出 V V V的个数和 ∧ ∧ ∧的个数。
输入格式
第一行一个数 n n n;
第二行是 n n n个数,分别代表 y 1 , y 2 , ⋯   , y n y1,y2,\cdots,yn y1,y2,⋯,yn。
输出格式
两个数,中间用空格隔开,依次为 V V V的个数和 ∧ ∧ ∧的个数。
数据范围
10 % 10\% 10%的数据 n ≤ 600 n\le600 n≤600;
40 % 40\% 40%的数据 n ≤ 5000 n\le5000 n≤5000;
100 % 100\% 100%的数据 n ≤ 200000 n\le200000 n≤200000,答案不超过 i n t 64 int64 int64。
分析
首先要理解题意,就是要求一个序列中满足中间比两边大和中间比两边小的三元组个数。可以想到,若一个序列中的数 a i a_i ai,在 i i i之前有 p p p个数比它小,在 i i i之后有 q q q个数比它小,则根据乘法原理,答案中 ∧ ∧ ∧的个数就要加上 p ∗ q p*q p∗q,然后问题就转变为查找在 i i i之前比 a i a_i ai小的数,在 i i i之前比 a i a_i ai大的数,在 i i i之后比 a i a_i ai小的数,在 i i i之后比 a i a_i ai大的数的个数。这个东西就可以用权值树状数组或权值线段树维护。由于本题数据为 1 1 1~ n n n的一个排列,所以就不用离散化了。
代码
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
const int N=200005;
typedef long long LL;
int n,a[N];
LL c[N],f[N][5],ans1,ans2;
#define lowbit(x) ((x)&-(x))
void Updata(int x,int v) {
while (x<=n) {
c[x]+=v;
x+=lowbit(x);
}
}
LL Ask(int x) {
LL res=0;
while (x) {
res+=c[x];
x-=lowbit(x);
}
return res;
}
int main() {
scanf("%d",&n);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
for (int i=1;i<=n;i++) {//在i前面比a[i]小
f[i][1]=Ask(a[i]-1);
Updata(a[i],1);
}
memset(c,0,sizeof(c));
for (int i=n;i>=1;i--) {//在i后面比a[i]小
f[i][2]=Ask(a[i]-1);
Updata(a[i],1);
}
for (int i=1;i<=n;i++) a[i]=n-a[i]+1;//将大小变换,方便求大的数的个数
memset(c,0,sizeof(c));
for (int i=1;i<=n;i++) {//在i前面比现在的a[i]小的数,由于大小变换了
f[i][3]=Ask(a[i]-1);//所以相当于求i前面比原来的a[i]大的数的个数
Updata(a[i],1);
}
memset(c,0,sizeof(c));
for (int i=n;i>=1;i--) {//在i后面比原来a[i]大的数
f[i][4]=Ask(a[i]-1);
Updata(a[i],1);
}
for (int i=1;i<=n;i++) {
ans1+=(f[i][3]*f[i][4]);//枚举累加答案
ans2+=(f[i][1]*f[i][2]);
}
printf("%lld %lld",ans1,ans2);
return 0;
}