BZOJ 1112 [POI2008]砖块Klo(可持久化线段树)

【题目链接】 http://www.lydsy.com/JudgeOnline/problem.php?id=1112

【题目大意】

  给出一个数列,对于一个操作,你可以对一个数+1,或者一个数-1,
  问若使得数列中出现长度为m的连续相同的数,最少需要的操作数。

【题解】

  我们发现对于固定区间求最小操作,等价于求区间中数距离中位数差值和最小,
  我们发现区间中位数可以利用主席树求区间kth来实现,
  同时在主席树上维护权值线段树的区间真值和,那么对于每个区间中的数,
  就能分别维护比中位数小的部分的和以及比中位数大的部分的和,
  分别与中位数的倍数做加减运算即可。
  我们枚举区间的位置,保留最小值,复杂度O(nlogn)。

【代码】

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=100010;
typedef long long LL;
int n,m,i,x,y,z,ans;
int l[N*40],r[N*40],v[N*40],tot,root[N],num[N];
LL s[N*40];
int cmp(int i,int j){return num[i]<num[j];}
int rk[N],sa[N];  //sa:该数字对应原数列的下标 
int build(int a,int b){
    int x=++tot; v[x]=s[x]=0;
    if(a==b)return x;
    int mid=(a+b)>>1;
    return l[x]=build(a,mid),r[x]=build(mid+1,b),x;
}
// x版本c位置+p,在s中记录+num[sa[c]],统计权值线段树上真值的区间和。
int change(int x,int a,int b,int c,int p){
    int y=++tot;v[y]=v[x]+p;s[y]=s[x]+num[sa[c]];
    if(a==b)return y;
    int mid=(a+b)>>1;
    if(c<=mid)l[y]=change(l[x],a,mid,c,p),r[y]=r[x];
    else l[y]=l[x],r[y]=change(r[x],mid+1,b,c,p);
    return y;
}
int kth(int x,int y,int a,int b,int k){
    if(a==b)return a;
    int mid=(a+b)>>1;
    if(v[l[y]]-v[l[x]]>=k)return kth(l[x],l[y],a,mid,k);
    else return kth(r[x],r[y],mid+1,b,k-v[l[y]]+v[l[x]]);
}
LL abs(LL x){return x<0?-x:x;}
LL query(int x,int y,int a,int b,int c){
    if(a==b)return 1LL*abs(num[sa[a]]-num[sa[c]])*(v[y]-v[x]);
    int mid=(a+b)>>1;
    if(c<=mid){
        return query(l[x],l[y],a,mid,c)+s[r[y]]-s[r[x]]-1LL*num[sa[c]]*(v[r[y]]-v[r[x]]);
    }else{
        return query(r[x],r[y],mid+1,b,c)+1LL*num[sa[c]]*(v[l[y]]-v[l[x]])-(s[l[y]]-s[l[x]]);
    }
} 
int main(){
    while(~scanf("%d%d",&n,&m)){
        root[0]=0;tot=0; 
        for(int i=1;i<=n;i++)scanf("%d",num+i);  
        for(int i=1;i<=n;i++)sa[i]=i;  
        sort(sa+1,sa+n+1,cmp); 
        for(int i=1;i<=n;i++)rk[sa[i]]=i;
        build(1,n);
        for(int i=1;i<=n;i++)root[i]=change(root[i-1],1,n,rk[i],1);
        LL ans=0x3f3f3f3f3f3f3f3f;
        for(int i=1;i<=n-m+1;i++){
            int id=kth(root[i-1],root[i+m-1],1,n,(m+1)>>1);
            ans=min(ans,query(root[i-1],root[i+m-1],1,n,id));
        }printf("%lld
",ans);
    }return 0;
}
原文地址:https://www.cnblogs.com/forever97/p/bzoj1112.html