宣传一下算法提高课整理 <—
写在前面
这两天没更新,因为每天都有OI的集训,早八晚九
于是就鸽了6篇题解没写
→ 题目传送门点这里 ←
前置知识
1. 求一个数二进制表示中最低的非零位,使用lowbit函数。
C++演示:
int lowbit(int x)
{
return x & -x;
}
2. 数据结构:树状数组
基本操作:
- 求区间$[L, R]$的和, 时间复杂度$O(\log n)$
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)$
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$ 个点的水平位置和竖直位置是两两不同的。
西部 314 认为这幅壁画所包含的信息与这 $n$个点的相对位置有关,因此不妨设坐标分别为 $(1,y_1),(2,y_2),…,(n,y_n)$
,其中 $y_1∼y_n$ 是 $1$ 到 $n$ 的一个排列。
西部 314 打算研究这幅壁画中包含着多少个图腾。
如果三个点 $(i,y_i),(j,y_j),(k,y_k)$ 满足 $1≤i<j<k≤n$ 且 $y_i>y_j,y_j<y_k$,则称这三个点构成 V 图腾;
如果三个点 $(i,y_i),(j,y_j),(k,y_k)$满足 $1≤i<j<k≤n$ 且 $y_i<y_j,y_j>y_k$,则称这三个点构成 ∧ 图腾;
西部 314 想知道,这 $n$ 个点中两个部落图腾的数目。
因此,你需要编写一个程序来求出 V
的个数和 ∧
的个数。
输入格式
第一行一个数 $n$。
第二行是 $n$ 个数,分别代表 $y_1,y_2,…,y_n$.
输出格式
两个数,中间用空格隔开,依次为 V
的个数和 ∧
的个数。
数据范围
对于所有数据,$n≤200000$,且输出答案不会超过 int64
。
$y_1∼y_n$ 是 $1$ 到 $n$ 的一个排列。
样例输入
5
1 5 3 2 4
样例输出
3 4
题目化简
设这三个点纵坐标分别为i, j, k;
这道题就是让我们求呈V
或∧
的3个点的数量。
V : i > j < k
^ : i < j > k
思路
我们发现,对于每一个点$A(x, y),$既可以将$y$轴分为$[0,y-1]$与$[y+1,n]$两段区间,又可以将$x$轴分为$[0,x-1]$与$[x+1,n]$两段区间。
根据乘法原理,我们可以用树状数组维护$[0,x-1]$与$[x+1,n]$两段区间中纵坐标大于(或小于)y(既纵坐标位于$[y+1,n]$区间)的点的个数,用high[]和low[]数组存储,再将它们相乘。
具体做法:
1. 从左向右依次遍历$a[i]$,使用树状数组统计它前面比它大和比它小的点的个数
在统计完成后,将$a[i]$加入树状数组
2. 清空树状数组
3. 从右向左依次遍历$a[i]$,使用树状数组统计它后面比它大和比它小的点的个数
在统计完成后,将$a[i]$加入树状数组
算法时间复杂度
对于每一个点都使用树状数组,因此时间复杂度为$O(n\log n)$
AC Code
$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;
}
最后,如果觉得对您有帮助的话,点个赞再走吧!