原题链接:https://www.acwing.com/problem/content/243/
最傻逼的做法:
// 最垃圾的做法 当然是tle了
#include<iostream>
using namespace std;
const int N = 200010;
typedef long long LL;
LL a[N];
int main()
{
int n;
cin >> n;
for(int i=0; i<n; i++) scanf("%d",&a[i]);
LL res_1 = 0, res_2 = 0;
for(int i=0; i<n-2; i++)
for(int j=i+1; j<n-1; j++)
for(int k=j+1; k<n; k++)
{
if(a[i] < a[j] && a[k] < a[j]) res_2++;
if(a[i] > a[j] && a[k] > a[j]) res_1++;
}
printf("%lld %lld\n",res_1, res_2);
return 0;
}
一般的做法
//有技巧的暴力:继续超时。
// (暴力枚举) O(n2)
// 一种朴素做法就是遍历所有点i, 分别统计i位置左边比a[i]小的数的个数m、右边比a[i]小的数的个数n,运用乘法原理:
// 1. 第一步从左边m个数中任选一个,有m种选法
// 2. 第二步从右边n个数中任选一个,有n种选法
// 那么在i位置组成图腾∧的方案数一共是 m * n。
// 累加每个点的方案数,即为所有组成图腾∧的方案总数。
// 时间复杂度O(n2)。
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 2000010;
typedef long long LL;
int a[N];
//ll[i]表示i的左边比第i个数小的数的个数
//rl[i]表示i的右边比第i个数小的数的个数
//lg[i]表示i的左边比第i个数大的数的个数
//rg[i]表示i的右边比第i个数大的数的个数
int ll[N], rl[N], lg[N], rg[N];
int main()
{
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
for(int i = 1; i <= n; i++)
{
for(int j = 1; j < i; j++)
{
//a[]保存的是1 ~ n的一个排列,不可能相等
if(a[j] < a[i]) ll[i] ++;
else lg[i] ++;
}
}
for(int i = 1; i <= n; i++)
{
for(int j = n; j > i; j--)
{
if(a[j] < a[i]) rl[i] ++;
else rg[i] ++;
}
}
LL resV = 0, resA = 0;
for(int i = 1; i <= n; i++)
{
resV += (LL)lg[i] * rg[i];
resA += (LL)ll[i] * rl[i];
}
printf("%lld %lld\n", resV, resA);
return 0;
}
使用树状数组对查询和插入操作进行优化。之前插入是O(1), 查询是O(n). 现在查询和插入都是O(logn)
对于每个数a[i], 我们只需要知道i的左边和右边分别有多少数小于a[i], 然后这俩数相乘就是对于a[i] 来说’∩’的个数。 依次遍历每个a[i], 求和就是总个数。
朴素的做法总的时间复杂度n方。使用树状数组后变成nlogn。下面详细解释一下。
因为输入数是一个全排列,不会有重复的数。而对于每个a[i],我们想知道他的左边有多少大于他的数,有多少小于他的数,对于右边亦是如此。
我们采用哈希的思想,如果一个数y出现了,那么c[y] 就为1。如果我们不使用树状数组,每次判断有多少个数小于y,就需要遍历一遍c数组,这样的 时间复杂度O(n)
但是对于数组c,我们可以使用树状数组t[]来维护c的前缀和。sum(x)返回c[1~x]的前缀和。
这样每次求前边有多少个数小于y,就直接就sum(y-1)就行了。表示范围再1~y-1的数,总共
出现过多少次,时间复杂度是O(logn).
全程都在直接维护c的树状数组t,c数组都没有出现过!!!
求左边:
求左边有多少个数<y: sum(y-1)表示数组左边有多少小于等于y-1的数。
求左边有多少个数>y: sum(n) - sum(y) 即小于等于n的数的总和 - 小于等于y的数的总和.
初始化树状数组t[]: 每次添加一个a[i], 使用add(a[i], 1) 函数,表示在c数组中a[i]的位置
也就是c[a[i]] += 1,同时结果体现在树状数组t中。维护数组数组。c自始至终就没有出现过。
求右边:
a[i] 加进来之后再c[a[i]]进行标记,同时维护tr[],
因为求得是右边有多少个,因此需要从右往左遍历a[i]
这样右边的a[i]才能先于左边的a[i]出现。 小于继续求sum(y-1)。大于继续求sum(n) - sum(y)
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 200010;
int n;
int a[N];
int tr[N];
int Greater[N], lower[N];
int lowbit(int x)
{
return x & -x;
}
void add(int x, int c)
{
for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}
int sum(int x)
{
int res = 0;
for (int i = x; i; i -= lowbit(i)) res += tr[i];
return res;
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
for (int i = 1; i <= n; i ++ )
{
int y = a[i];
Greater[i] = sum(n) - sum(y);
lower[i] = sum(y - 1);
add(y, 1);
}
memset(tr, 0, sizeof tr);
LL res1 = 0, res2 = 0;
for (int i = n; i; i -- )
{
int y = a[i];
res1 += Greater[i] * (LL)(sum(n) - sum(y));
res2 += lower[i] * (LL)(sum(y - 1));
add(y, 1);
}
printf("%lld %lld\n", res1, res2);
return 0;
}