楼兰图腾

楼兰图腾

题目描述

在这里插入图片描述


题目解释

题目会给定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,y1]的点的个数的总和。那么我们可以应用树状数组就可以求出纵坐标在区间 [ 1 , y − 1 ] [1,y-1] [1,y1]的点的个数的总和。

对于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,y1]的点的个数的总和。那么我们可以应用树状数组就可以求出纵坐标在区间 [ 1 , y − 1 ] [1,y-1] [1,y1]的点的个数的总和。

因此,对于题目输入的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=1nm×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;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卷心菜不卷Iris

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值