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