AcWing算法提高课-4.2.1楼兰图腾

宣传一下算法提高课整理 <—

csdn

写在前面

这两天没更新,因为每天都有OI的集训,早八晚九
于是就鸽了6篇题解没写

题目传送门点这里

前置知识

1. 求一个数二进制表示中最低的非零位,使用lowbit函数。

C++演示:

int lowbit(int x)
{
	return x & -x;
}

2. 数据结构:树状数组

基本操作:
  • 求区间 [ L , R ] [L, R] [L,R]的和, 时间复杂度 O ( log ⁡ n ) O(\log n) O(logn)

C++演示:

int qsum(int x)
{
	int res = 0;
	for (int i = x; i; i -= lowbit(i))
		res += tr[i];
	return res;
}
  • 将第x个数加上c, 时间复杂度 O ( log ⁡ n ) O(\log n) O(logn)

C++演示:

void add(int x, int c)
{
	for (int i = x; i <= n; i += lowbit(i))
		tr[i] += c;
}

题目描述

在完成了分配任务之后,西部 314 来到了楼兰古城的西部。

相传很久以前这片土地上(比楼兰古城还早)生活着两个部落,一个部落崇拜尖刀(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),…,(n,y_n) (1,y1),(2,y2),,(n,yn)
,其中 y 1 ∼ y n y_1∼y_n y1yn 1 1 1 n n n 的一个排列。

西部 314 打算研究这幅壁画中包含着多少个图腾。

如果三个点 ( i , y i ) , ( j , y j ) , ( k , y k ) (i,y_i),(j,y_j),(k,y_k) (i,yi),(j,yj),(k,yk) 满足 1 ≤ i < j < k ≤ n 1≤i<j<k≤n 1i<j<kn y i > y j , y j < y k y_i>y_j,y_j<y_k yi>yj,yj<yk,则称这三个点构成 V 图腾;

如果三个点 ( i , y i ) , ( j , y j ) , ( k , y k ) (i,y_i),(j,y_j),(k,y_k) (i,yi),(j,yj),(k,yk)满足 1 ≤ i < j < k ≤ n 1≤i<j<k≤n 1i<j<kn 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 的个数和 的个数。

输入格式

第一行一个数 n n n

第二行是 n n n 个数,分别代表 y 1 , y 2 , … , y n y_1,y_2,…,y_n y1y2,,yn.

输出格式

两个数,中间用空格隔开,依次为 V 的个数和 的个数。

数据范围

对于所有数据, n ≤ 200000 n≤200000 n200000,且输出答案不会超过 int64
y 1 ∼ y n y_1∼y_n y1yn 1 1 1 n n n 的一个排列。

样例输入

5
1 5 3 2 4

样例输出

3 4

题目化简

a
设这三个点纵坐标分别为i, j, k;
这道题就是让我们求呈V的3个点的数量。
V : i > j < k
^ : i < j > k

思路

我们发现,对于每一个点 A ( x , y ) , A(x, y), A(x,y),既可以将 y y y轴分为 [ 0 , y − 1 ] [0,y-1] [0,y1] [ y + 1 , n ] [y+1,n] [y+1,n]两段区间,又可以将 x x x轴分为 [ 0 , x − 1 ] [0,x-1] [0,x1] [ x + 1 , n ] [x+1,n] [x+1,n]两段区间。

根据乘法原理,我们可以用树状数组维护 [ 0 , x − 1 ] [0,x-1] [0,x1] [ x + 1 , n ] [x+1,n] [x+1,n]两段区间中纵坐标大于(或小于)y(既纵坐标位于 [ y + 1 , n ] [y+1,n] [y+1,n]区间)的点的个数,用high[]和low[]数组存储,再将它们相乘。

具体做法:

  1. 从左向右依次遍历 a [ i ] a[i] a[i],使用树状数组统计它前面比它大和比它小的点的个数
    在统计完成后,将 a [ i ] a[i] a[i]加入树状数组
  2. 清空树状数组
  3. 从右向左依次遍历 a [ i ] a[i] a[i],使用树状数组统计它后面比它大和比它小的点的个数
    在统计完成后,将 a [ i ] a[i] a[i]加入树状数组

算法时间复杂度

对于每一个点都使用树状数组,因此时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)

AC Code

C + + C++ C++

#include <iostream>
#include <cstring>

using namespace std;

typedef long long LL; 

const int N = 200010;

int n;
int a[N];
int tr[N];
int low[N], high[N];

int lowbit(int x) // 求x的二进制表示的最后一位1 
{
	return x & -x;
}

void add(int x, int c) // 快速修改某个数,让a[x] + c,O(logn) 
{
	for (int i = x; i <= n; i += lowbit(i))
		tr[i] += c;
}

int qsum(int x) // 快速求1~x的和,O(logn) 
{
	int res = 0;
	for (int i = x; i; i -= lowbit(i))
		res += tr[i];
	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 ++ )
	{
		int y = a[i];
		high[i] = qsum(n) - qsum(y); // 比a[i]大的点的个数
		low[i] = qsum(y - 1); // 比a[i]小的点的个数
		add(y, 1);
	}
	
	memset(tr, 0, sizeof(tr)); // 清空树状数组
	LL r1 = 0, r2 = 0; 
	for (int i = n; i; i -- )
	{
		int y = a[i];
		r1 += high[i] * (LL)(qsum(n) - qsum(y)); // v图腾 
		r2 += low[i] * (LL)(qsum(y - 1)); // ^图腾 
		add(y, 1);
	}
	
	printf("%lld %lld\n", r1, r2);
	
	return 0;
}

a

最后,如果觉得对您有帮助的话,点个赞再走吧!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

星河依旧长明

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

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

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

打赏作者

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

抵扣说明:

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

余额充值