【bzoj4889】[Tjoi2017]不勤劳的图书管理员 树状数组+分块+二分
题目描述(转自洛谷)
加里敦大学有个帝国图书馆,小豆是图书馆阅览室的一个书籍管理员。他的任务是把书排成有序的,所以无序的书让他产生厌烦,两本乱序的书会让小豆产生这两本书页数的和的厌烦度。现在有n本被打乱顺序的书,在接下来m天中每天都会因为读者的阅览导致书籍顺序改变位置。因为小豆被要求在接下来的m天中至少要整理一次图书。小豆想知道,如果他前i天不去整理,第i天他的厌烦度是多少,这样他好选择厌烦度最小的那天去整理。
输入
第一行会有两个数,n,m分别表示有n本书,m天
接下来n行,每行两个数,ai和vi,分别表示第i本书本来应该放在ai的位置,这本书有vi页,保证不会有放置同一个位置的书
接下来m行,每行两个数,xj和yj,表示在第j天的第xj本书会和第yj本书会因为读者阅读交换位置
n、m<=50000
输出
一共m行,每行一个数,第i行表示前i天不去整理,第i天小豆的厌烦度,因为这个数可能很大,所以将结果模10^9 +7后输出
样例输入
5 5
1 1
2 2
3 3
4 4
5 5
1 5
1 5
2 4
5 3
1 3
样例输出
42
0
18
28
48
题解
树状数组+分块+二分
一开始看到n、m<=50000写了个区间线段树套权值线段树,结果写了垃圾回收却仍然MLE+RE(也许区间线段树套SBT可能不会MLE或RE,但TLE的事也不好说)
所以不能过分相信nlog^2n数据结构,分块才是王道。
那么本题就和 bzoj3343 相似。
先用树状数组预处理出一开始的答案(正着扫一遍,反过来再扫一遍)。
然后考虑:交换两个数,只对它们中间的数产生影响。所以只需要统计出它们中间有多少个比x大的,比x小的,比y大的,比y小的。
考虑它们原来对答案的贡献,和交换后对答案的贡献,把ans加加减减就好了。
我们可以分块实现这个过程,对于每个块把该块的数排序,并维护前缀和。查询时,对于整块可以二分查找,零碎的部分暴力完成。
最后再判断x和y是否会产生逆序对,并更新答案即可。
注意特判x和y在同一个块内的情况。
另外,本题的ans不用在计算的过程中取模,直接使用long long类型记录,最后输出时取个模就行了。
时间复杂度$O(n\sqrt n\log n)$
#include <cmath> #include <cstdio> #include <algorithm> #define N 50010 using namespace std; typedef long long ll; struct data { int p; ll w; bool operator<(const data a)const {return p < a.p;} }a[N]; struct block { data val[250]; int lp , rp; ll sum[250]; void build() { int i; for(i = 1 ; i <= rp - lp + 1 ; i ++ ) val[i] = a[i + lp - 1]; sort(val + 1 , val + rp - lp + 2); for(i = 1 ; i <= rp - lp + 1 ; i ++ ) sum[i] = sum[i - 1] + val[i].w; } ll qsmall(int pos) { int l = 1 , r = rp - lp + 1 , mid , ans = l - 1; while(l <= r) { mid = (l + r) >> 1; if(val[mid] < a[pos]) ans = mid , l = mid + 1; else r = mid - 1; } return ans * a[pos].w + sum[ans]; } ll qbig(int pos) { int l = 1 , r = rp - lp + 1 , mid , ans = r + 1; while(l <= r) { mid = (l + r) >> 1; if(a[pos] < val[mid]) ans = mid , r = mid - 1; else l = mid + 1; } return (rp - lp - ans + 2) * a[pos].w + sum[rp - lp + 1] - sum[ans - 1]; } }b[250]; int bl[N] , f[N] , g[N] , n; void add1(int x , ll a) {int i; for(i = x ; i ; i -= i & -i) f[i] += a;} ll query1(int x) {int i; ll ans = 0; for(i = x ; i <= n ; i += i & -i) ans += f[i]; return ans;} void add2(int x , ll a) {int i; for(i = x ; i <= n ; i += i & -i) g[i] += a;} ll query2(int x) {int i; ll ans = 0; for(i = x ; i ; i -= i & -i) ans += g[i]; return ans;} int main() { int m , i , si , x , y; ll ans = 0 , t1 , t2 , t3 , t4; scanf("%d%d" , &n , &m) , si = (int)sqrt(n); for(i = 1 ; i <= n ; i ++ ) scanf("%d%lld" , &a[i].p , &a[i].w) , bl[i] = (i - 1) / si + 1; for(i = 1 ; i <= bl[n] ; i ++ ) b[i].lp = (i - 1) * si + 1 , b[i].rp = min(i * si , n) , b[i].build(); for(i = 1 ; i <= n ; i ++ ) ans += query1(a[i].p + 1) , add1(a[i].p , a[i].w); for(i = n ; i >= 1 ; i -- ) ans += query2(a[i].p - 1) , add2(a[i].p , a[i].w); while(m -- ) { scanf("%d%d" , &x , &y); if(x == y) {printf("%lld\n" , ans % 1000000007); continue;} if(x > y) swap(x , y); t1 = t2 = t3 = t4 = 0; if(y - x > 1) { if(bl[x] == bl[y]) for(i = x + 1 ; i < y ; i ++ ) t1 += (a[i] < a[x]) * (a[i].w + a[x].w), t2 += (a[x] < a[i]) * (a[i].w + a[x].w) , t3 += (a[i] < a[y]) * (a[i].w + a[y].w) , t4 += (a[y] < a[i]) * (a[i].w + a[y].w); else { for(i = bl[x] + 1 ; i < bl[y] ; i ++ ) t1 += b[i].qsmall(x) , t2 += b[i].qbig(x) , t3 += b[i].qsmall(y) , t4 += b[i].qbig(y); for(i = x + 1 ; i <= b[bl[x]].rp ; i ++ ) t1 += (a[i] < a[x]) * (a[i].w + a[x].w), t2 += (a[x] < a[i]) * (a[i].w + a[x].w) , t3 += (a[i] < a[y]) * (a[i].w + a[y].w) , t4 += (a[y] < a[i]) * (a[i].w + a[y].w); for(i = b[bl[y]].lp ; i < y ; i ++ ) t1 += (a[i] < a[x]) * (a[i].w + a[x].w), t2 += (a[x] < a[i]) * (a[i].w + a[x].w) , t3 += (a[i] < a[y]) * (a[i].w + a[y].w) , t4 += (a[y] < a[i]) * (a[i].w + a[y].w); } ans = ans - t1 + t2 + t3 - t4; } if(a[x] < a[y]) ans += a[x].w + a[y].w; else ans -= a[x].w + a[y].w; printf("%lld\n" , ans % 1000000007); swap(a[x] , a[y]); b[bl[x]].build() , b[bl[y]].build(); } return 0; }