HDU 5497 Inversion(树状数组)

树状数组好题。   我们都知道可以用树状数组求一个序列的逆序数,但是之前一直没有深刻理解为什么要用树状数组,通过该题可以知道,我们正确的思维应该是先面对一个问题,然后思考如何解决问题,然后才能对其中遇到的困难有一个深刻的认识,然后就能知道为什么需要这样解决,为什么要用树状数组。因为我们需要这样的数据结构。

该题要求删去一个长度为m的连续序列后逆序数最小值。

由于长度是固定的,所以由滑动窗口我们可以联想到。不妨将这个窗口也从左向右移动,看看会发生什么。

每一次移动,显然会往这个序列中删除一个数,增加一个数,那么对答案产生了怎样的影响呢?

1.加入一个数:多了它后面所有比它小的数,多了它前面所有比它大的数

2.删除一个数:少了它后面所有比它小的数,多了它前面所有比它大的数

那么这样我们用两个树状数组动态维护删除的序列前面和后面部分。   这样就可以在O(n*log(n))的时间内求出答案了。

注意:删除操作和加入操作要分开进行,不能混淆。

另外一开始TLE了,因为数组太大,连memset也不行了,所以我们不妨限制清空内存的大小即可。

细节参见代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
using namespace std;
typedef long long ll;
const double eps = 1e-6;
const int INF = 1000000000;
const int maxn = 100000+5;
int T,n,m,a[maxn];
ll b[maxn],c[maxn];
ll sum(ll *bit, ll i) {
    ll s = 0;
    while(i > 0) {
        s += bit[i];
        i -= i & -i;
    }
    return s;
}
void add(ll *bit, ll i, ll x) {
    while(i <= n) {
        bit[i] += x;
        i += i & -i;
    }
}
int main() {
    scanf("%d",&T);
    while(T--) {
        scanf("%d%d",&n,&m);
        memset(b,0,(n+3)*sizeof(ll));
        memset(c,0,(n+3)*sizeof(ll));
        ll cur = 0, ans = INF;
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        for(int i=1+m;i<=n;i++) {
            cur += i-m-1-sum(b,a[i]);
            add(b,a[i], 1);
        }
        ans = cur;
        for(int i=m+1;i<=n;i++) {
            cur += sum(b,a[i-m]-1);
            cur += sum(c,n) - sum(c,a[i-m]);
            add(c,a[i-m],1);
            cur -= sum(b,a[i]-1);
            cur -= sum(c,n) - sum(c,a[i]);
            add(b,a[i],-1);
            ans = min(ans,cur);
        }
        printf("%I64d\n",ans);
    }
    return 0;
}


你可能感兴趣的:(HDU,树状数组,ACM-ICPC)