Problem Description
Given a sequence A with length n,count how many quadruple (a,b,c,d) satisfies: a≠b≠c≠d,1≤a < b≤n,1≤c < d≤n,Aa < Ab,Ac > Ad.Input
The input consists of multiple test cases.
Each test case begin with an integer n in a single line.The next line contains n integers A1,A2⋯An.
1≤n≤50000
0≤Ai≤1e9Output
For each test case,output a line contains an integer.Source
2016 Multi-University Training Contest 5
离散化
多校的时候,还不是很了解树状数组的。所以当时一直没想到这一点。
用树状数组来维护该点左右两侧的大小个数情况,确实很方便。
但是,数据的大小为1e9,数组开不了那么大,那怎么办呢?因为数据量为1e5,做一下离散化就行,大概的作用就是让原本稀疏的数据变得的密集起来,方便放入数据里。方法是将较大的值等价的用较小的值来进行替代。实现的代码如下:
int n;//数据大小
map<int, int> m;
int a[maxn], b[maxn];
int main(){
//录入数据到数组a中,以1开始
b = a;
sort(b+1, b+n+1);
for(int i = 1; i <= n; i++)
m[b[i]] = i;//建立数据和较小值的映射关系
for(int i = 1; i <= n; i++)
a[i] = m[a[i]]//映射到a数组里
}
树状数组
接下来就是 树状数组来实现 逆序对的统计。
说实在的,这里我刚开始也不懂,为什么可以这样写。看了大神一下午的代码,笔算了一下,感觉是比较懂了。
首先,不要被树状数组给吓到了。树状数组只是为了提高效率,其实统计也可以用普通的数组来实现,代码如下:
int a[maxn];
int c[maxn];
int ans[maxn];
int main(){
// 录入数据到a数组里 以1开始
for(int i = 1; i <= n; i++){
int sum = 0;
for(int j = 1; j < i; j++){
sum += c[j];
}
ans[i] = sum;
c[a[i]] = 1;//这一步很关键,将i左侧出现的数字都标记上,有点桶排序的感觉 看完这里 再看上面就很好理解了。
}
}
对应的上个树状数组的来理解一下,就比较容易了。
// 数组情况和上面一致,add sum方法 和模板一致
int main(){
for(int i = 1; i <= n; i++){
ans[i] = sum[a[i]-1];
add(a[i],1);
}
}
wokaka,原来这么简单,还让我想了一下午。。。
因为离散化的时候,对应的映射值都是不一样的,所以在 i 左侧的值非大即小。知道这一点后,很容易求左侧大的值。同理,求右侧的时候,只需倒着输入数组a的值,来建立树状数组就行了。
容斥
就下来就是容斥了,其实也说不上是容斥原理,倒是去重更为准确。两个逆序对嘛,肯定有重复的,不满足题意的。这里用ls,lb,rs,rb来表示一点左侧比该点小数,左侧比该点较大的数,右侧较小,较大的数。
不满足的情况一共有如下几种:
- a == c , b > d —– 枚举a,rs*rb
- a < c , b == d —– 枚举b,ls*lb
- a == d —–枚举a,rb*lb
b == c —–枚举b,rs*ls;
其实 枚举谁并不重要,只要遍历一下,每一项的(ls+rb)x(lb+rs), 加在一起就可以了。不过现在只能从上面四种情况推出 (ls+rb)*(lb+rs),感觉这个式子应该也有含义,还没有悟到,还望过路的大神指点。
ok 离散化 树状数组 容斥 都搞定了。
上AC代码:
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <queue>
#include <stack>
#include <string>
#include <bitset>
#include <cstdio>
#include <limits>
#include <vector>
#include <climits>
#include <cstring>
#include <cstdlib>
#include <fstream>
#include <numeric>
#include <sstream>
#include <iostream>
#include <algorithm>
#define MEM(a,x) memset(a,x,sizeof(a))
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
map<int, int> t;
int a[50010];
int b[50010];
long long x[50010];
long long y[50010];
long long xx[50010];
long long yy[50010];
long long tree[200000];
long long ans, aa, bb;
int n, i;
int add(int k,int x)
{
for ( ; k <= n; k += k & -k)
{
tree[k] += x;
}
return 0;
}
long long sum(int k)
{
long long ans = 0;
for ( ; k > 0; k -= k & -k)
{
ans += tree[k];
}
return ans;
}
int main()
{
// freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
while (~scanf("%d", &n))
{
t.clear();
for (i = 1; i <= n; ++i)
{
scanf("%d", &a[i]);
b[i] = a[i];
}
sort(b+1, b+1+n);
for (i = 1; i <= n; ++i)
t[b[i]] = i;
for (i = 1; i <= n; ++i)
a[i] = t[a[i]];
x[0] = 0;
aa = 0;
memset(tree, 0, sizeof(tree));
for (i = 1; i <= n; ++i)
{
x[i] = sum(a[i]-1);//左侧比当前数小的个数
yy[i] = sum(a[i]) - sum(a[i]-1);
add(a[i], 1);
aa += x[i];
yy[i] = i-1 - x[i] - yy[i];//左侧比当前数大的个数
}
y[0] = 0;
bb = 0;
memset(tree, 0, sizeof(tree));
for (i = n; i >= 1; --i)
{
y[i] = sum(a[i]-1);//右侧比当前数小的个数
xx[i] = sum(a[i]) - sum(a[i]-1);
add(a[i], 1);
bb += y[i];
xx[i] = (n - i) - y[i] - xx[i];//右侧比当前数大的个数
}
ans = aa * bb;
for (i = 1; i <= n; ++i)
{
ans -= (x[i] + xx[i]) * (y[i] + yy[i]);
}
printf("%lld\n", ans);
}
return 0;
}