楼兰图腾
题目描述
题目解释
题目会给定n个点,然后这n个点的横坐标已经固定了(即1~n),但是题目会输入这n个点的纵坐标,但是这n个点的纵坐标是不确定的,唯一能够确定的是这n个点的纵坐标的范围是在1和n之间。
题目要求我们从给定的这n个点中,选择某些点,构成V图腾,求出V图腾的个数;
题目要求我们从给定的这n个点中,选择某些点,求出能够构成^图腾的个数。
如图,由图可知,能够构成V图腾的个数有3个
如图,由图可知,能够构成^图腾的个数有4个。
核心思路
对于V图腾来说,假设我们当前枚举的某个点A的坐标为 ( x , y ) (x,y) (x,y),我们可以从左往右扫描题目输入的纵坐标,那么我们应该算出点A的左边有多少个点的纵坐标是大于 y y y的,即找到纵坐标在区间 [ y + 1 , n ] [y+1,n] [y+1,n]的点的个数的总和。而这和树状数组的查询操作吻合,因为树状数组的查询函数getSum(int x)可以返回区间 [ 1 , x ] [1,x] [1,x]内的整数的总和。那么我们巧妙地应用树状数组就可以求出纵坐标在区间 [ y + 1 , n ] [y+1,n] [y+1,n]的点的个数的总和,首先求出 s u m [ n ] sum[n] sum[n]表示纵坐标在区间 [ 1 , n ] [1,n] [1,n]的点的个数的总和,再求出 s u m [ y ] sum[y] sum[y]表示纵坐标在区间 [ 1 , y ] [1,y] [1,y]的点的个数的总和,那么 s u m [ n ] − s u m [ y ] sum[n]-sum[y] sum[n]−sum[y]就表示纵坐标在区间 [ y + 1 , n ] [y+1,n] [y+1,n]的点的个数的总和。对于^图腾来说,我们可以从左往右扫描题目输入的纵坐标,那么我们应该算出点A的左边有多少个点的纵坐标是==小于 y y y==的,即找到纵坐标在区间 [ 1 , y − 1 ] [1,y-1] [1,y−1]的点的个数的总和。那么我们可以应用树状数组就可以求出纵坐标在区间 [ 1 , y − 1 ] [1,y-1] [1,y−1]的点的个数的总和。
对于V图腾来说,假设我们当前枚举的某个点A的坐标为 ( x , y ) (x,y) (x,y),我们可以从右往左扫描题目输入的纵坐标,那么我们应该算出点A的右边有多少个点的纵坐标是大于 y y y的,即找到纵坐标在区间 [ y + 1 , n ] [y+1,n] [y+1,n]的点的个数的总和。我们可以应用树状数组就可以求出纵坐标在区间 [ y + 1 , n ] [y+1,n] [y+1,n]的点的个数的总和,即 s u m [ n ] − s u m [ y ] sum[n]-sum[y] sum[n]−sum[y]。对于^图腾来说,我们可以从右往左扫描题目输入的纵坐标,那么我们应该算出点A的右边有多少个点的纵坐标是==小于 y y y==的,即找到纵坐标在区间 [ 1 , y − 1 ] [1,y-1] [1,y−1]的点的个数的总和。那么我们可以应用树状数组就可以求出纵坐标在区间 [ 1 , y − 1 ] [1,y-1] [1,y−1]的点的个数的总和。
因此,对于题目输入的n个纵坐标来说,我们可以把这n个纵坐标分成n个小集合,对于每一个小集合纵坐标a[i],我们要找到它左边有多少个点的纵坐标是大于a[i]的(假设有m个),找到它右边有多少个点的纵坐标是大于a[i]的(假设有n个),那么由乘法原理可知,对于该纵坐标a[i]来说,它可以从左边的m个中随便挑出一个,共有m中挑法,它可以从右边的n个中随便挑出一个,共有n中挑法,那么总共有 m × n m\times n m×n中挑法可以对于a[i]这个纵坐标来说,是能够构成V图腾的。因为有n个纵坐标,所有总共能够构成V图腾的总数为: ∑ i = 1 n m × n \sum \limits _{i=1}^{n}m\times n i=1∑nm×n。对于^图腾,同理分析。
- 从左往右依次遍历题目输入的纵坐标a[i],使用树状数组统计在纵坐标a[i]左边有多少个点的纵坐标是大于a[i]的,以及有多少个点的纵坐标是小于a[i]的。
- 从右往左依次遍历题目输入的纵坐标a[i],舒勇树状数组统计在纵坐标a[i]右边有多少个点的纵坐标是大于a[i]的,以及由多少个点的纵坐标是小于a[i]的。
- 统计完成以后,把当前枚举的这个纵坐标a[i]加入树状数组中。
- 时间复杂度是 O ( l o g n ) O(logn) O(logn)。
代码
#include<iostream>
#include<cstring>
using namespace std;
const int N=200010;
typedef long long LL;
int n;
int a[N],C[N]; //C[i]用来存储某个纵坐标出现的次数
//ll[i]表示点i的左边有多少个比点i的纵坐标小的点的个数
//rl[i]表示点i的右边有多少个比点i的纵坐标小的点的个数
//lg[i]表示点i的左边有多少个比点i的纵坐标大的点的个数
//rg[i]表示点i的右边有多少个比点i的纵坐标大的点的个数
int ll[N], rl[N], lg[N], rg[N];
int lowbit(int x)
{
return x&(-x);
}
//更新操作
void update(int x,int v)
{
for(int i=x;i<=n;i+=lowbit(i))
{
C[i]+=v;
}
}
//查询操作
int getSum(int x)
{
int sum=0;
for(int i=x;i>0;i-=lowbit(i))
{
sum+=C[i];
}
return sum;
}
int main()
{
scanf("%d",&n);
//输入n个点的纵坐标
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
//从左往右依次枚举输入的这n个点的纵坐标
for(int i=1;i<=n;i++)
{
int y=a[i]; //获得当前枚举到的这个点的纵坐标
lg[i]=getSum(n)-getSum(y);//统计这个点的左边有多少个纵坐标比y还大的点的个数 V
ll[i]=getSum(y-1); //统计这个点的左边有多少个纵坐标比y还小的点的个数 ^
update(y,1); //将y加入树状数组,表示这个纵坐标y已经出现1次
}
//清空树状数组,不然会影响后面的操作
memset(C,0,sizeof C);
LL resV=0,resA=0; //resV记录V图腾的个数 resA记录^图腾的个数
//从右往左依次枚举输入的这n个点的纵坐标
for(int i=n;i>0;i--)
{
int y=a[i];//获得当前枚举到的这个点的纵坐标
rg[i]=getSum(n)-getSum(y);//统计这个点的右边有多少个纵坐标比y还大的点的个数 V
rl[i]=getSum(y-1);//统计这个点的右边有多少个纵坐标比y还小的点的个数 ^
//对于V图腾来说,枚举当前最低点的纵坐标y,算出了左边有lg[i]个纵坐标比y还大的点的个数
//算出了右边有rg[i]个纵坐标比y还大的点的个数,所以对于这个最低点的纵坐标y来说,可以构成lg[i]*rg[i]个V图腾
//总共可以枚举到n个最低点的纵坐标,因此,一共有resV+=(LL)lg[i]*rg[i]
resV+=(LL)lg[i]*rg[i];//lg[i]*rg[i]可能会爆int,所以转换为long long
resA+=(LL)ll[i]*rl[i];
update(y,1);
}
printf("%lld %lld\n",resV,resA);
return 0;
}