洛谷P3157/BZOJ3295[CQOI2011]动态逆序对

分块是个好算法

尤其对于我这种不会树套树的傻子

删除一个数时,不难发现它对于逆序对的贡献是它前面比它大的数的个数+它后面比它小的数的个数,于是用分块就非常容易了:

1. 算贡献,该数前面的块和后面的块二分,它所在的块暴力;
2. 把它删除,原数列打标记,块内删除后把后面的数往前移。

这样写的复杂度是$O(m\sqrt{n}logn)$的,相当危险,但是我们发现把块大小取成$\sqrt{nlogn}$的话,复杂度就降到了$O(m\sqrt{nlogn})$,不用卡常都可以轻松AC。

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=100050;
char rB[1<<21],*S,*T,wB[1<<21];
int wp=-1;
inline char gc(){return S==T&&(T=(S=rB)+fread(rB,1,1<<21,stdin),S==T)?EOF:*S++;}
inline void flush(){fwrite(wB,1,wp+1,stdout);wp=-1;}
inline void pc(char c){if(wp+1==(1<<21))flush();wB[++wp]=c;}
inline int rd(){
    char c=gc();
    while(c<48||c>57)c=gc();
    int x=c&15;
    for(c=gc();c>=48&&c<=57;c=gc())x=(x<<3)+(x<<1)+(c&15);
    return x;
}
short buf[25];
inline void wt(ll x){
    short l=-1;
    while(x>9){
        buf[++l]=x%10;
        x/=10;
    }
    pc(x|48);
    while(l>=0)pc(buf[l--]|48);
    pc('\n');
}
//快读/快写
int a[N],b[100][1300],c[N],t[N],n,x,sz[100],K,cnt,pos[N];
ll ans=0;
bool d[N];
void ms(int L,int R){  //归并排序求初始逆序对数
    if(L==R)return;
    int M=L+R>>1,i=L,j=M+1,k=L;
    ms(L,M);ms(M+1,R);
    while(i<=M&&j<=R)if(c[i]<=c[j])t[k++]=c[i++];
    else{
        ans+=M-i+1;
        t[k++]=c[j++];
    }
    while(i<=M)t[k++]=c[i++];
    while(j<=R)t[k++]=c[j++];
    for(i=L;i<=R;i++)c[i]=t[i];
}
inline void build(){
    int i,j;
    K=sqrt(n*log(n)/log(2));if(!K)K=1;cnt=n/K;  //1*log(1)=0,碰上毒瘤数据(这题没有)会RE
    for(i=1;i<=cnt;i++){
        for(j=1;j<=K;j++)b[i][j]=a[(i-1)*K+j];
        sort(b[i]+1,b[i]+K+1);
        sz[i]=K;
    }
    if(n%K){
        cnt++;
        for(i=1;i<=n%K;i++)b[cnt][i]=a[(cnt-1)*K+i];
        sort(b[cnt]+1,b[cnt]+n%K+1);
        sz[cnt]=n%K;
    }
}
inline void inupper(int p,int x){  //块内二分更大值的个数
    int l=1,r=sz[p],m,s=sz[p]+1;
    while(l<=r){
        m=l+r>>1;
        if(b[p][m]>x){s=m;r=m-1;}
        else l=m+1;
    }
    ans-=sz[p]-s+1;
}
inline void inlower(int p,int x){  //块内二分更小值的个数
    int l=1,r=sz[p],m,s=0;
    while(l<=r){
        m=l+r>>1;
        if(b[p][m]<x){s=m;l=m+1;}
        else r=m-1;
    }
    ans-=s;
}
inline int find(int p,int x){  //暴力搜
    int l=1,r=sz[p],m;
    while(l<r){
        m=l+r>>1;
        if(b[p][m]>=x)r=m;
        else l=m+1;
    }
    return l;
}
inline void work(){
    int p=(x+K-1)/K,i,t=find(p,a[x]);
    for(i=1;i<p;i++)inupper(i,a[x]);
    for(i=p+1;i<=cnt;i++)inlower(i,a[x]);
    for(i=(p-1)*K+1;i<x;i++)if(!d[i]&&a[i]>a[x])ans--;
    for(i=x+1;i<=p*K&&i<=n;i++)if(!d[i]&&a[i]<a[x])ans--;
    d[x]=1;  //删除标记
    for(i=t;i<sz[p];i++)b[p][i]=b[p][i+1];  //删除一个数后后面的数要前移
    sz[p]--;  //此块内元素减少
}
int main(){
    int m,i;
    n=rd();m=rd()-1;  //既然最后一个不用输出答案,那就不用处理了
    for(i=1;i<=n;i++)pos[a[i]=rd()]=i;  //题目要删除的是数,所以要记下位置
    build();
    memcpy(c,a,sizeof(c));
    ms(1,n);
    wt(ans);
    while(m--){
        x=pos[rd()];
        work();
        wt(ans);
    }
    flush();
    return 0;
}
View Code

然后我就一直学不会树套树

猜你喜欢

转载自www.cnblogs.com/sunshine-chen/p/11258807.html