[BZOJ4889][Tjoi2017]不勤劳的图书管理员-分块-树状数组

不勤劳的图书管理员

Description

加里敦大学有个帝国图书馆,小豆是图书馆阅览室的一个书籍管理员。他的任务是把书排成有序的,所以无序的书让他产生厌烦,两本乱序的书会让小豆产生这两本书页数的和的厌烦度。现在有n本被打乱顺序的书,在接下来m天中每天都会因为读者的阅览导致书籍顺序改变位置。因为小豆被要求在接下来的m天中至少要整理一次图书。小豆想知道,如果他前i天不去整理,第i天他的厌烦度是多少,这样他好选择厌烦度最小的那天去整理。

Input

第一行会有两个数,n,m分别表示有n本书,m天

接下来n行,每行两个数,ai和vi,分别表示第i本书本来应该放在ai的位置,这本书有vi页,保证不会有放置同一个位置的书

接下来m行,每行两个数,xj和yj,表示在第j天的第xj本书会和第yj本书会因为读者阅读交换位置

Output

一共m行,每行一个数,第i行表示前i天不去整理,第i天小豆的厌烦度,因为这个数可能很大,所以将结果模10^9 +7后输出

Sample Input

5 5
1 1
2 2
3 3
4 4
5 5
1 5
1 5
2 4
5 3
1 3

Sample Output

42
0
18
28
48

HINT

对于20%的数据,1 ≤ ai; xj; yj ≤ n ≤ 5000, m ≤ 5000, vi ≤ 10^5
对于100%的数据,1 ≤ ai; xj; yj ≤ n ≤ 50000, m ≤ 50000, vi ≤ 10^5


爆炸OJ日常题面爆炸……
居然不放题面上来……
还好洛谷有题面……

另外调了整整两个小时啊啊啊啊啊

(╯‵□′)╯︵┻━┻


思路:
首先使用树状数组计算初始答案,可以发现互相形成逆序关系的数对具有贡献,使用两个bit,一个像普通的计算逆序对一般维护,另一个插入数字时插入v[i],即可计算初始答案。

对于每一次交换位置,可以发现所有与a[x]和a[y]改变了逆序关系的数字的贡献会改变,而这些改变仅会出现在[x,y]区间的数与x或y之间。

那么考虑分块,对每个块内部进行排序,对于交换的位置之间的完整块,在块内二分,a值小于二分目标的所有值的贡献的变化相同(就是被二分到的位置切开的两段的贡献分别需要在答案中加上和减去)。
此时记个排序后的块内v值的前缀和,对x和y均进行二分,即可做到单块操作 O(logn)

对于不完整的块,考虑暴力计算贡献的变化,并暴力重新排序及计算前缀和。复杂度同样为 O(logn)

直接分成 O(n) 块,复杂度为 O(nlogv+mnlogn) ,解决问题~

细节巨多无比,需要仔细检察代码……

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>

using namespace std;

inline int read()
{
    int x=0;char ch=getchar();
    while(ch<'0' || '9'<ch)ch=getchar();
    while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
    return x;
}

typedef long long ll;
const int M=1e5+9;
const int N=50009;
const ll md=1e9+7;

int n,q;
int st[N],ed[N],id[N],a[N];
ll ans,sum[N],v[N],bit1[M],bit2[M];

inline void cal(ll &x){x%=md;if(x<0)x=(x%md+md)%md;}

inline bool cmp(int x,int y)
{
    return a[x]<a[y];
}

inline void add(ll *bit,int p,ll v)
{
    for(int i=p;i<M;i+=(i&-i))
        bit[i]=((bit[i]+v)%md+md)%md;
}

inline ll query(ll *bit,int p)
{
    ll ret=0;
    for(int i=p;i;i-=(i&-i))
        (ret+=bit[i])%=md;
    return ret;
}

int main()
{
    n=read();q=read();
    int blk=sqrt(n);
    for(int i=1;i<=n;i++)
    {
        a[i]=read();
        v[i]=read()%md;
        id[i]=i;
        if(!st[i/blk])
        {
            st[i/blk]=i;
            if(i-1)
                ed[(i-1)/blk]=i-1;
        }
    }
    ed[n/blk]=n;

    for(int i=0,e=n/blk;i<=e;i++)
    {
        sort(id+st[i],id+ed[i]+1,cmp);
        sum[st[i]]=v[id[st[i]]];
        for(int j=st[i]+1;j<=ed[i];j++)
            sum[j]=(sum[j-1]+v[id[j]])%md;
    }

    ans=0;
    for(int i=n;i>=1;i--)
    {
        ans=(ans+query(bit1,a[i])+query(bit2,a[i])*v[i]%md)%md;
        add(bit1,a[i],v[i]);add(bit2,a[i],1);
    }

    while(q--)
    {
        int x=read(),y=read();
        if(x>y)swap(x,y);
        int lb=x/blk,rb=y/blk;

        if(lb==rb)
        {
            for(int i=x;i<=y;i++)
            {
                if(x<i)
                {
                    if(a[x]<a[i])
                        cal(ans+=(v[x]+v[i])%md);
                    else
                        cal(ans-=(v[x]+v[i])%md);
                }
                if(i!=x && i<y)
                {
                    if(a[i]<a[y])
                        cal(ans+=(v[y]+v[i])%md);
                    else
                        cal(ans-=(v[i]+v[y])%md);
                }
            }

            swap(a[x],a[y]);
            swap(v[x],v[y]);
            sort(id+st[lb],id+ed[lb]+1,cmp);
            sum[st[lb]]=v[id[st[lb]]];
            for(int j=st[lb]+1;j<=ed[lb];j++)
                sum[j]=(sum[j-1]+v[id[j]])%md;

            cal(ans);
            printf("%lld\n",ans);
            continue;
        }

        for(int i=x+1;i<=ed[lb];i++)
            if(a[x]<a[i])
                cal(ans+=v[x]+v[i]);
            else
                cal(ans-=v[x]+v[i]);
        for(int i=st[rb];i<=y;i++)
            if(a[x]<a[i])
                cal(ans+=v[x]+v[i]);
            else
                cal(ans-=v[x]+v[i]);

        for(int i=x+1;i<=ed[lb];i++)
            if(a[i]<a[y])
                cal(ans+=v[y]+v[i]);
            else
                cal(ans-=v[y]+v[i]);
        for(int i=st[rb];i<y;i++)
            if(a[i]<a[y])
                cal(ans+=v[y]+v[i]);
            else
                cal(ans-=v[y]+v[i]);

        for(int i=lb+1;i<rb;i++)
        {
            int l=st[i],r=ed[i],val=st[i]-1,mid;
            while(l<=r)
            {
                mid=l+r>>1;
                if(a[id[mid]]<a[x])
                    l=mid+1,val=mid;
                else
                    r=mid-1;
            }

            cal(ans+=sum[ed[i]]-2*sum[val<st[i]?0:val]%md);
            cal(ans+=v[x]*(ed[i]-2*val+st[i]-1)%md);

            l=st[i],r=ed[i],val=st[i]-1;
            while(l<=r)
            {
                mid=l+r>>1;
                if(a[id[mid]]<a[y])
                    l=mid+1,val=mid;
                else
                    r=mid-1;
            }
            cal(ans+=2*sum[val<st[i]?0:val]%md-sum[ed[i]]);
            cal(ans+=v[y]*(2*val-ed[i]-st[i]+1)%md);
        }

        swap(a[x],a[y]);
        swap(v[x],v[y]);
        sort(id+st[lb],id+ed[lb]+1,cmp);
        sort(id+st[rb],id+ed[rb]+1,cmp);

        sum[st[lb]]=v[id[st[lb]]];
        for(int j=st[lb]+1;j<=ed[lb];j++)
            sum[j]=(sum[j-1]+v[id[j]])%md;
        sum[st[rb]]=v[id[st[rb]]];
        for(int j=st[rb]+1;j<=ed[rb];j++)
            sum[j]=(sum[j-1]+v[id[j]])%md;
        cal(ans);
        printf("%lld\n",ans);
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值