洛谷题单指南-线段树-P1637 三元上升子序列
原题链接:https://www.luogu.com.cn/problem/P1637
题意解读:统计序列a[1]~a[n]中三元上升子序列的个数,三元上升子序列是指对于1<=i<j<k<=n有a[i]<a[j]<a[k],(a[i],a[j],a[k])成为一组上升子序列。
解题思路:
1、先思考一下暴力,通过三重循环枚举i,j,k找到所有i<j<k时符合a[i]<a[j]<a[k]的数量即可,显然会超时。
2、再进一步思考,要统计所有三元上升子序列的个数,对于每一个a[i],只要知道a[1]~a[i-1]中<a[i]的数组成的所有二元上升子序列的个数,在每个二元上升子序列后追加一个a[i],即可得到以a[i]结尾的三元上升子序列的个数,把以所有a[1]~a[n]结尾的三元上升子序列的个数加起来就是答案。
3、那么问题就转化成,对于一个a[i],如何统计a[1]~a[i-1]中<=a[i]的所有二元上升子序列的个数?
只需要计算a[1]~a[i-1]中所有以<=a[i]的数结尾的二元上升子序列个数,求和即可。
设s[i]表示以i结尾的的所有二元上升子序列的个数,sum1[i]表示1~i的个数
那么可以分析得到:s[i] = sum[i-1],考虑到元素可能重复,所以有:s[i] += sum1[i-1]
这样一来,在计算以a[i]结尾的三元上升子序列个数时,只需s[1]+s[2]+...+s[a[i]-1],
因此需要维护s的前缀和,设为sum2[i]表示s[1]+...+s[i]
4、要快速计算sum1[i],可以定义一个h[i]表示元素i的个数,这样sum[i] = h[1] + ... + h[i],sum就是h的前缀和数组
5、通过遍历a[1]、a[2]...a[n],对于每一个a[i],将sum2[a[i]-1]累加到答案,并记录其个数h[a[i]]++,这样就能够实现h数组的动态更新,边更新h[a[i]],边计算sum1[a[i]],再更新s[a[i]],累加即得答案。
因此,本题的关键就在于对h数组和s数组进行单点修改以及区间查询,通过树状数组或者线段树都可以解决。
这里,采用两个树状数组来分别维护h和s。
100分代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 100005; //注意a的范围是1~100000
int tr1[N], tr2[N], a[N]; //tr1对应h,tr2对应s
int n;
long long ans;
int lowbit(int x)
{
return x & -x;
}
void add1(int x, int val)
{
//注意i取值最大不是n,而是N
for(int i = x; i <= N; i += lowbit(i)) tr1[i] += val;
}
int sum1(int x)
{
int res = 0;
for(int i = x; i != 0; i -= lowbit(i)) res += tr1[i];
return res;
}
void add2(int x, int val)
{
//注意i取值最大不是n,而是N
for(int i = x; i <= N; i += lowbit(i)) tr2[i] += val;
}
int sum2(int x)
{
int res = 0;
for(int i = x; i != 0; i -= lowbit(i)) res += tr2[i];
return res;
}
int main()
{
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= n; i++)
{
ans += sum2(a[i] - 1); //以a[i]结尾的三元上升子序列的个数=所有小于a[i]结尾的二元上升子序列个数之和
add2(a[i], sum1(a[i] - 1)); //更新以a[i]结尾的二元上升子序列个数=所有小于a[i]的个数
add1(a[i], 1); //更新a[i]的个数
}
cout << ans;
return 0;
}