【题解】B - 三元上升子序列
题目内容
原题:洛谷P1637
Description
Erwin 最近对一种叫thair
的东西巨感兴趣。。。
在含有 \(n\) 个整数的序列\(a_1,a_2,\ldots,a_n\) 中,三个数被称作thair
当且仅当 \(i\lt j\lt k\) 且 \(a_i\lt a_j\lt a_k\)。
求一个序列中 thair 的个数。
Input
开始一行一个正整数 \(n\),
以后一行 \(n\) 个整数 \(a_1,a_2,\ldots,a_n\)。
Output
一行一个整数表示thair
的个数。
数据规模与约定
- 对于 \(30\%\) 的数据 保证 \(n\le100\);
- 对于 \(60\%\) 的数据 保证 \(n\le2000\);
- 对于 \(100\%\) 的数据 保证 \(1\le n\le3\times10^4\),\(1\le a_i\le 10^5\)。
思路
我们可以通过统计以 \(a_i\) 开头的、以 \(a_i\) 结尾的、以 \(a_i\) 作为中间点的thair
个数来统计答案。而在这三种方法里,无疑是最后一种最好实现。对于一个 \(a_i\),我们只需要统计满足 \(j<i\And a_j<a_i\) 的 \(j\) 的个数 \(k_1\),满足 \(j>i\And a_j>a_i\) 的 \(j\) 的个数 \(k_2\),相乘即可得到 \(a_i\) 对于答案的贡献。对于每个 \(a_i\),分开算它们的 \(k_1\) 和 \(k_2\)。
这里以算 \(k_1\) 为例来介绍实现步骤。从前往后遍历,每到一个位置就把这个值加入一个支持单点修改、查询前缀和的数据结构,数据结构开在值域上,加数就把对应位置的值 \(+1\),找答案就直接查询前缀和即可。由于 \(j\) 严格小于 \(i\) 所以要先统计在把当前位置的数加入数据结构(当然,由于 \(a_j\) 严格小于 \(a_i\),所以不注意顺序也无所谓)。\(k_2\) 同理。
至于使用的数据结构,可以是树状数组、线段树或值域分块(好像还有CDQ分治)。这里给出值域分块的解法,复杂度 \(O(n\sqrt{n})\)。其余算法的复杂度为 \(O(n\log n)\)。
代码
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ri register int
#define inf 0x3f3f3f3f
int a,b[30003],c[30003];
long long ans;
struct Block_Array//块状数组封装
{
#define N 100001
#define M 404
int cnt,len,lt[M],rt[M],num[M],col[N],be[N];
il void build(int x)//分块
{
len=sqrt(x);
cnt=x/len;
fill(col+1,col+1+x,0);
fill(num+1,num+1+cnt,0);
for(ri i=1;i<=cnt;i++)
{
lt[i]=rt[i-1]+1;
rt[i]=rt[i-1]+len;
}
if(rt[cnt]!=x)
{
cnt++;
lt[cnt]=rt[cnt-1]+1;
rt[cnt]=x;
}
for(ri i=1;i<=cnt;i++)
{
for(ri j=lt[i];j<=rt[i];j++)
{
be[j]=i;
}
}
}
il void clear(int x)//为了省时间所以反向之前清空存值数组而不是重新分块
{
fill(col+1,col+1+x,0);
fill(num+1,num+1+cnt,0);
}
il void add(int x)//加值
{
col[x]++;
num[be[x]]++;
}
il int find(int x,int y)//区间和查询
{
if(x>y)
{
return 0;
}
ri rn=0;
if(be[x]==be[y])
{
for(ri i=x;i<=y;i++)
{
rn+=col[i];
}
}
else
{
for(ri i=x;i<=rt[be[x]];i++)
{
rn+=col[i];
}
for(ri i=be[x]+1;i<=be[y]-1;i++)
{
rn+=num[i];
}
for(ri i=lt[be[y]];i<=y;i++)
{
rn+=col[i];
}
}
return rn;
}
#undef N
#undef M
}ba;
int main()
{
scanf("%d",&a);
ba.build(100000);
for(ri i=1;i<=a;i++)
{
scanf("%d",&b[i]);
c[i]=ba.find(1,b[i]-1);
ba.add(b[i]);
}
ba.clear(100000);
for(ri i=a;i>=1;i--)
{
ans+=c[i]*ba.find(b[i]+1,100000);
ba.add(b[i]);
}
printf("%lld",ans);
return 0;
}