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