三元上升子序列

写这道题需要树状数组的知识点,不清楚的话可以看一看

目录

题目大意:

分析题意:

确定思路,制定策略,选定算法:

碰到问题,思考解决方案(有时可以更换思路)


题目大意:

       题目传送门

       给定一个长度为n的序列,求其中包含多少个三元上升子序列。

 

分析题意:

       没有坑,也没什么好分析的。直接进入下一步——

确定思路,制定策略,选定算法:

       首先,我们想到的肯定是O_{\left( n^{2} \right )}的暴力枚举:每次枚举中间的数字,从前看有几个数字比他小,往后看有几个数字比它大。但是,1\leq n\leq 3*10^{4},不能说肯定,只能说有120%的概率会超时。因此,我们要找一个更快的方式,对于任意的i\in a_{i},都可以以小等于logn的时间复杂度求出来。

       你现在有没有想到,到处学习用树状数组求逆序对呢?如果想到了,恭喜你,你离构建出完整的思路已经不远了:

在树状数组求逆序对中,我们先建立两个数组,然后排序其中一个,得到数组中各个数字的“排名”,然后按照排名一个个在树状数组中插入数字。因为数字都是从小到大插入的,所以现在插入的数字一定比之前插入过的所有数字都来得大,因此只要知道在它之前有几个数字就可以了,这可以用树状数组以O_{logn}的时间复杂度解决。同时,在插入完毕一个数字之后,再用树状数组修改前缀和,还是O_{logn}的时间复杂度。 

这样,我们就可以以O_{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;
}

感言:树状数组是个好东西。但是一定要想好要不要用,该怎么用。这样方可写出难题。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值