Hdu 5147 Sequence II(树状数字 or 线段树 + 输入外挂 前缀和+后缀和)

题意:

给定1~n的一个排列 用A[]数组保存,问有多少下标(a,b,c,d)四元组满足:
a < b < c < d 且 A[a] < A[b],A[c] < A[d]

解析:

题目中n的范围是50000,O(n^2) 复杂度肯定超时。那么这题明显考察的是log2(n)的算法,对于这题可以用线段树或者树状数组,同时要用到输入外挂,不然会超时。

思路(参考别人做法)

枚举c的位置,那么每一次枚举中的方法数为 1~c-1 中(a,b)的个数 乘以 c~n中(c,d)的个数。累加起来即为答案。

1~c-1中(a,b)的个数相当于枚举b的位置,然后计算出b前面有多少数比A[b]小,该值要保存下来,下一次枚举c的时候,该值再加上c-1前面有多少比a[c-1]小的数即为当前情况下1~c-1中(a,b)的个数,也就是b=c-1的时候,因为枚举b之前的情况已经算过了。

因为当算c时只是算出 比c小的方法数,这个只是满足比c小的二元组的个数。而c前面的二元组也要计算在内,所以要加上c前面的比c小的个数。

用树状数组来做。本题n范围50000,而且每个数都不相同很关键。所以我们就开辟n个位置,一开始每个位置都是0,其实每个位置不是0就是1,因为每个数只有一个。

比如 1 3 2 4 5
一开始 c数组 0 0 0 0 0
先统计,再输入,因为计算a[i]前面有多少比它小的数,不包括它自己,而树状数组计算和的时候,要包括它自己。
i=1,树状数组求和前缀和 pre[1]=0 ,
此时0 0 0 0 0, 输入1,变为 1 0 0 0 0
i=2,a[2]=3,要看 3前面有多少个数,也就是看c数组的3个位置前面有多少个1,1代表已经输入,
发现1 0 0 0 0前三个数只有一个1,也就是pre[2]=1 (输入的第二个数之前只有1个比它小的),输入3以后,c数组变为 1 0 1 0 0
i=3, a[3]= 2, 要看2前面有多少个数,也就是看c数组前2个位置前面有多少个1,发现10100前两个数中只有一个1,也就是pre[3]=1

前缀和可以算出来,那么后缀和 = num2[i] = n-i-a[i]+num[i]+1;

AC代码

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 5e4 + 10;
int n;
ll C[N], a[N];
inline void read(ll &x) {
    int flag = 0;
    x = 0;
    char c = getchar();
    if(c == '-')
        flag = 1;
    while(c < '0' || c > '9') {
        if(c == '-')
            flag = 1;
        c = getchar();
    }
    while(c >= '0' && c <= '9')
        x = x * 10 + c - '0', c = getchar();
    if(flag) x = -x;
}
inline int lowbit(int x) {
    return x & (-x);
}
int query(int x) {
    int ret = 0;
    while(x > 0) {
        ret += C[x];
        x -= lowbit(x);
    }
    return ret;
}
void add(int x, int d) {
    while(x <= n) {
        C[x] += d;
        x += lowbit(x);
    }
}
int num[N] , num2[N];
int main() {
    int T;
    scanf("%d", &T);
    while(T--) {
        ll ans = 0, pre = 0;
        scanf("%d", &n);
        memset(C, 0, sizeof(C));
        for(int i = 1; i <= n; i++) {
            read(a[i]);
            num[i] = query(a[i]); //计算a[i]之前比a[i]小的数字
            num2[i] = n-i-a[i]+num[i]+1; //计算a[i]之后比a[i]大的数字
            add(a[i], 1);
            ans += pre * num2[i];
            pre += num[i];
        }
        printf("%lld\n", ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值