【洛谷每日一题P1637】 三元上升子序列(树状数组+二分)

写在题解前的话

写这道题的契机是本小白在前一天的Acwing周赛中没有看出第三题是一道树状数组的模板题(或者说本人完全没有独立完成过任何一道不带算法标签的树状数组题,我是菜鸡呜呜呜),因此决定在洛谷中刷几个小时的树状数组题加深印象。于是在刷了几道简单的板子题后,正好发现了这道绿题,虽然是绿题,但是个人认为对于板子没有非常大的拓展。

显然本人的题解是不如洛谷上的大多数题解的,emm,也有可能是不如全部题解。不过考虑到日后进行算法模块复习的时候树状数组和线段树这部分是不能忽略的,我还是选择写下这篇博客(就当是单纯记录自己的刷题状态吧嘻嘻嘻)。

我个人题解部分在变量以及函数命名的可理解性方面不是很强,只是单纯为了短时间内刷完题而进行了变量的命名,我会在程序中加入一定的批注,方便程序的理解。

洛谷原题摘录

题目描述

Erwin 最近对一种叫 thair 的东西巨感兴趣。。。

在含有 n n n 个整数的序列 a 1 , a 2 , … , a n a_1,a_2,\ldots,a_n a1,a2,,an 中,三个数被称作thair当且仅当 i < j < k i<j<k i<j<k a i < a j < a k a_i<a_j<a_k ai<aj<ak

求一个序列中 thair 的个数。

输入格式

开始一行一个正整数 n n n,

以后一行 n n n 个整数 a 1 , a 2 , … , a n a_1,a_2,\ldots,a_n a1,a2,,an

输出格式

一行一个整数表示 thair 的个数。

样例 #1

样例输入 #1

4
2 1 3 4

样例输出 #1

2

样例 #2

样例输入 #2

5
1 2 2 3 4

样例输出 #2

7

提示

样例2 解释

7 7 7thair 分别是:

  • 1 2 3
  • 1 2 4
  • 1 2 3
  • 1 2 4
  • 1 3 4
  • 2 3 4
  • 2 3 4
数据规模与约定
  • 对于 30 % 30\% 30% 的数据 保证 n ≤ 100 n\le100 n100
  • 对于 60 % 60\% 60% 的数据 保证 n ≤ 2000 n\le2000 n2000
  • 对于 100 % 100\% 100% 的数据 保证 1 ≤ n ≤ 3 × 1 0 4 1 \leq n\le3\times10^4 1n3×104 0 ≤ a i ≤ 1 0 5 0\le a_i\leq 10^5 0ai105

个人理解

首先看到这道题,第一时间就应该想到是用树状数组进行解题,因为树状数组的板子题就包括了这类的顺序对和逆序对题。

其次需要注意的就是不同于一般的逆序对题,这次需要处理的是一个三元问题。并且从输入样例2中就可以发现测试数据中每个数组都可能存在大小相同的值,因此在进行二分时需要明确知道当前二分得到的值属于相等数值区间中的具体哪一个,比较考验细心程度。

最后就是在求值时,只需要关注三元组中大小排名第二,也就是最中间的那个值所处状态就行,如果单纯考虑三元组的头或者是尾,非常容易发生漏选和复选现象,而且判断起来的状态也比较复杂。每次循环遍历时只需要加上数组左侧(下标较小一侧)数值较小的数据个数与数组右侧(下标较大一侧)数值较大的数据个数的乘积,再分别对树状数组存储的数据进行更新即可。

每次循环计算三元组个数时,需要用到的数据包括:原数组升序排序后当前数据所在的位置(如果该数据在数组中不止一次出现,需要两次二分分别得到第一次出现和最后一次出现的下标位置),此前原数组中已经出现过(比当前遍历下标更小)的小于和小于等于当前遍历数值的数据数量。显然数组左侧(下标较小一侧)数值较小的数据个数已经通过树状数组的查询操作得到,而数组右侧(下标较大一侧)数值较大的数据个数则需要通过运算,由剩余数据总个数-已经出现的数据个数得到

用到的板子

这里的板子直接用的是题解中的函数,不具有通用性,仅供参考!!!

二分

这里仅仅是其中一种写法

int find(int x)
{
	int l=0,r=n-1;
	while (l < r)
	{
		int mid = l + r >> 1;
		if (a[mid] >= x) r=mid;
		else l=mid+1;
	}
//	返回的是存在数据相等情况时的第一个
	return l;
}

树状数组全套

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

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

ll query(int x)
{
	ll res=0;
	for (int i=x; i; i-=lowbit(i))
	{
		res+=tr[i];
	}
	return res;
}

AC代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
typedef long long ll;
//4个数组的功能如下: w记录的输入数据的原始值,a保存w数组排序后的结果
//tr和tr2存储树状数组结点的值,方便统计对于下标为index的数据,0到index-1中存在多少小于、小于等于该值的数
int w[N],tr[N],a[N],tr2[N];
ll res,n;

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

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

ll query(int x)
{
	ll res=0;
	for (int i=x; i; i-=lowbit(i))
	{
		res+=tr[i];
	}
	return res;
}

void modify2(int u,int x)
{
	for (int i=u; i<=n; i+=lowbit(i))
	{
		tr2[i]+=x;
	}
}

ll query2(int x)
{
	ll res=0;
	for (int i=x; i; i-=lowbit(i))
	{
		res+=tr2[i];
	}
	return res;
}

int find(int x)
{
	int l=0,r=n-1;
	while (l < r)
	{
		int mid = l + r >> 1;
		if (a[mid] >= x) r=mid;
		else l=mid+1;
	}
//	返回的是存在数据相等情况时的第一个
	return l;
}

int find2(int x)
{
	int l=0,r=n-1;
	while (l < r)
	{
		int mid = l + r + 1 >> 1;
		if (a[mid] <= x) l=mid;
		else r=mid-1;
	}
//	返回的是存在数据相等情况时的最后一个
	return l;
}

int main()
{
	cin>>n;
	for (int i=0; i<n; i++) cin>>a[i];
	memcpy(w,a,sizeof a);
	sort(a,a+n);
	for (int i=0; i<n; i++)
	{
//		all返回的是一共有几个数在原数组中更小,因此如果存在相等情况要返回第一个下标
		int all=find(w[i]);
//		all2返回的是一共有几个数在原数组中小于等于原数据,因此如果存在相等情况要返回最后一个下标
		int all2=find2(w[i]);
//		树状数组结点从1开始,因此需要进行+1操作
//		now和now2分别查询此前已经出现过的小于和小于等于当前判断值的数据个数
		ll now=query(all+1);
		ll now2=query2(all+1);
//		now表示之前存在的比当前小的数 
//		n-1-i:后续剩余的所有数  (all2-now2):剩余小于等于w[i]的个数-已经出现的小于等于w[i]的个数
		res+=(ll)(now) * (n-1-i-(all2-now2));
//		modify(all2+1,1)表示对于所有大于当前数据的结点进行修改
//		modify2(all+1,1)表示对于所有大于等于当前数据的结点进行修改
		modify(all2+1,1);modify2(all+1,1);
	}
	cout<<res<<endl;
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值