写这道题需要树状数组的知识点,不清楚的话可以看一看。
目录
题目大意:
给定一个长度为n的序列,求其中包含多少个三元上升子序列。
分析题意:
没有坑,也没什么好分析的。直接进入下一步——
确定思路,制定策略,选定算法:
首先,我们想到的肯定是的暴力枚举:每次枚举中间的数字,从前看有几个数字比他小,往后看有几个数字比它大。但是,,不能说肯定,只能说有120%的概率会超时。因此,我们要找一个更快的方式,对于任意的,都可以以小等于logn的时间复杂度求出来。
你现在有没有想到,到处学习用树状数组求逆序对呢?如果想到了,恭喜你,你离构建出完整的思路已经不远了:
在树状数组求逆序对中,我们先建立两个数组,然后排序其中一个,得到数组中各个数字的“排名”,然后按照排名一个个在树状数组中插入数字。因为数字都是从小到大插入的,所以现在插入的数字一定比之前插入过的所有数字都来得大,因此只要知道在它之前有几个数字就可以了,这可以用树状数组以的时间复杂度解决。同时,在插入完毕一个数字之后,再用树状数组修改前缀和,还是的时间复杂度。
这样,我们就可以以的时间复杂度来解决求逆序对的问题了。
而在本题中,我们要求的是前面的出现过的数字和后面没出现过的数字。总体思路大同小异,使用同前缀和一样的思想,我们就可以写出下面的伪代码:
const int N = 3e4 + 3, M = 1e5 + 1;
unsigned long long ans;
int b[M];//b[i]表示值为i的数字在原数组中的位置
int n;
for (int i = 1; i < N; i++) {
if (b[i]==0) continue;
int qian, hou;
qian = ask(b[i] - 1);//求前缀和
hou = (n - b[i]) - (ask(n) - ask(b[i]));
//n-b[i]的意义是b[i]后面总共有多少个位置,
//ask(N)-ask(b[i])的意义是后面有多少个位置已经有数字了(即比a[b[i]]小)
ans += (unsigned long long)(qian * hou);
add(b[i]);
}
(完结撒花!)
事情没有这么简单!在数据中会有重复数字,且题目要求必须是严格上升(见样例)。所以,我们的思路还要有一定的修改。
碰到问题,思考解决方案(有时可以更换思路)
Q:有重复数字怎么办?
A:将b数组变为vector,相同大小的数字就存放在同一个vector中。
Q:那怎么确定时后面未插入的数字中有几个数字是相同的呢?
A:当然是排序啦!这样vector数组变得有序之后,问题就迎刃而解了!
下面上代码——
for (int i = 1; i < M; i++) {
sort(b[i].begin(), b[i].end());
int qian, hou,si=b[i].size();
for (int j = 0; j < si; j++) {
int to = b[i][j];
qian = ask(to - 1);
hou = (n - to) - (si - j - 1) - (ask(N) - ask(to));
ans += (unsigned long long)(qian * hou);
}
for (int j = 0; j < si; j++) {
add(b[i][j]);
}
}
代码同上方大同小异,相信各位一定可以看懂。
最后,加上树状数组,代码就大功告成了!
#include<iostream>
#include<algorithm>
#include<vector>
#define lowbit(x) x&(-x)
using namespace std;
const int N = 3e4 + 3, M = 1e5 + 1;
unsigned long long ans;
int n;
int a[N];
int c[N];
int tree[N];
vector<int>b[M];
void add(int x) {
while (x <= N) tree[x]++, x += lowbit(x);
}
int ask(int x) {
int sum = 0;
while (x) sum += tree[x], x -= lowbit(x);
return sum;
}
void init() {
//输入
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
//初始化
for (int i = 1; i <= n; i++) {
b[a[i]].push_back(i);
}//数组离散化
for (int i = 1; i < M; i++) {
sort(b[i].begin(), b[i].end());
}//将位置排序,为了之后可以快速得知在位置i之后有几个位置j满足a[i]=a[j]。因为序列是严格单调递增,所以这部分要减去。
}
signed main() {
init();
for (int i = 1; i < M; i++) {
int qian, hou,si=b[i].size();
for (int j = 0; j < si; j++) {
int to = b[i][j];
qian = ask(to - 1);
hou = (n - to) - (si - j - 1) - (ask(N) - ask(to));
ans += (unsigned long long)(qian * hou);
}
for (int j = 0; j < si; j++) {
add(b[i][j]);
}
}
cout << ans;
return 0;
}
感言:树状数组是个好东西。但是一定要想好要不要用,该怎么用。这样方可写出难题。