拿到这题,首先肯定能想到和树状数组求逆序数相联系,但一直在正向考虑,
中Ay前面比它小的数有多少,比它大的数有多少,然后乘法原理在减去那种不可
行的情况,不过不容易对于不可行的情况,很难找出求解方案。
所以,题解思路,从后往前找规律,对当前Ai,若其后面有能和Ai构成三角关
系的两个数,无非Ax<Az<Ay或者Ax<Ay<Az(x<y<z),前者求出Ax后面有几个
比其大的数然后 计算一个公差为一的等差数列的前n-1项和即可,后者求出Az前
面有几个比它小的数,后面后几个比它大的数,直接相乘即可。
注意在在Ai位置求的 差项是关于三个数中中间那个数的,但最终要求的答案
通过交换律可以变为所有的加项相加-所有减项相加,所以在倒序遍历时尽管不能
求出关于当前位置的减项,但其对最终答案的贡献不会被漏掉,会在前面求出。
(这算是解出本题的关键,
将最终结果表示的多项式通过交换律转化为容易求解出的形式)。
扩展:
本题因为是1~n的一组排列,所以树状数组可以在1~1e5范围上建立,
如果Ai的数据范围上升到1e9,这时就可以离散化一下。
AC代码:
/** Wjunjie **/
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include<algorithm>
#include <set>
#include <queue>
#include <stack>
#include<vector>
#include<map>
#include<ctime>
#define LL long long
using namespace std;
const int N=2e5+100;
const int M=100001*2;
const int mod=1e8+7;
int c[N];
void add(int x,int n)
{
for(;x<=n;x+=x&-x)c[x]+=1;
}
int ask(int x)
{
LL ans=0;
for(;x;x-=x&-x)ans+=c[x];
return ans;
}
int a[N];
int pre[N];
int last[N];
int main()
{
int cas=0;
int t;
scanf("%d",&t);
while(t--)
{
int n;
memset(c,0,sizeof(c));
memset(pre,0,sizeof(pre));
memset(last,0,sizeof(last));
scanf("%d",&n);
int maxs=-1;
for(int i=1;i<=n;++i)
{
scanf("%d",&a[i]);
if(a[i]>maxs)maxs=a[i];
}
//在a【i】左边且比其小的数
for(int i=1;i<=n;++i)
{
pre[i]=ask(a[i]);//这题不会有重复的数,不用考虑等于的问题;
add(a[i],maxs);
}
memset(c,0,sizeof(c));
LL ans=0;
for(int i=n;i>0;--i)
{
last[i]=n-i-ask(a[i]);//不用考虑等于的情况
add(a[i],maxs);//到处里long long 一下;
ans=(ans+(LL)((LL)last[i]*(last[i]-1)/2)%mod
-(LL)((LL)pre[i]*last[i])%mod)%mod;
//注意一下mod运算不能乱模;
}
printf("Case #%d: %lld\n",++cas,ans);
}
return 0;
}
The end;
因篇幅问题不能全部显示,请点此查看更多更全内容