【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;
}
上一篇:[转]ASP.NET Core 之 Identity 入门(三)


下一篇:bzoj 2527 Meteors - 整体二分 - 树状数组