BZOJ4167: 永远亭的竹笋采摘 分块

题意:给定一个序列,将其分成K段,每段元素不得全相同,求sigma(每段中不同元素差值的最小值)的最小值
n<=50000,k<=1000,序列中元素<=n,序列随机生成
既然序列是随机的,我们可以想到一个区间可能要延伸很长一段才会改变其中的最小差值,那么不妨将所有有贡献的点对都枚举出来(即:这个区间的两个端点之差是整个区间中最小的),问题就转化成了在数轴上有一些带权值的线段,选择K条不相交的使权值和最小,这个对于每个点枚举以他为结尾的线段进行转移即可。现在考虑如何求出所有点对。因为点对的分布很稀疏,暴力枚举就会枚举到大量无用的点对,那么不妨使用分块加速索引,每一块预处理出对于每一种权值块内元素中与其差值最小的是多少。那么对于每一个点,枚举后面的块,若最小差值能更新当前值就暴力在块内扫一遍,然后由于要保证两端点差值是最小的,因此枚举左端点时要从右往左,然后使用树状数组检查之前是否已经有点对右端点更靠左且差值更小即可。实测最后的点对数量是O(N)级别的。
代码:

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define gm 50001
#define abs _Abs
const int inf=0x7f7f7f7f;
int n,k,w,size,a[gm];
int c[gm];
int dis[gm][250];
inline int min(int a,int b){return a<b?a:b;}
inline int abs(int a){return a>>31?-a:a;}
struct e
{
    int t;
    e *n;
    int c;
    e(int t,e *n,int c):t(t),n(n),c(c){}
}*f[gm];
inline void push(int pos,int v)
{
    for(;pos<=n;pos+=pos&-pos)
    c[pos]=min(c[pos],v);
}
inline int get(int pos)
{
    int v=inf;
    for(;pos;pos-=pos&-pos)
    v=min(v,c[pos]);
    return v;
}
void setup()
{
    memset(c,0x7f,n+1<<2);
    static int b[250];
    for(int i=1;i<=w;++i)
    {
        int l=(i-1)*size+1,r=min(i*size,n);
        int top=0;
        for(;l<=r;++l) b[++top]=a[l];
        std::sort(b+1,b+top+1);
        int p1=0,p2=1;
        for(int j=1;j<=n;++j)
        {
            int &kre=dis[j][i]=inf;
            while(p1<top&&b[p1+1]<j) ++p1;
            while(p2<=top&&b[p2]<=j) ++p2;
            if(p1!=0) kre=min(kre,j-b[p1]);
            if(p2!=top+1) kre=min(kre,b[p2]-j);
        }
    }
}
void tour(int no,int block,int &ans,int beg)
{
    int end=min(block*size,n);
    for(;beg<=end;++beg)
    {
        int kre=abs(a[no]-a[beg]);
        if(kre&&kre<ans)
        {
            ans=kre;
            int cmp=get(beg);
            if(ans<cmp)
            {
                push(beg,ans);
                f[beg]=new e(no,f[beg],ans);
            }
        }
    }
}
int F[gm][1001];
int DP()
{
    memset(F,0x7f,sizeof F);
    F[0][0]=0;
    for(int i=1;i<=n;++i)
    {
        F[i][0]=0;
        for(int j=1;j<=k;++j)
        {
            int &ans=F[i][j]=F[i-1][j];
            for(e *l=f[i];l;l=l->n)
            ans=min(ans,F[l->t-1][j-1]+l->c);
            if(ans==inf) break;
        }
    }
    return F[n][k];
}
int main()
{
    scanf("%d%d",&n,&k);
    size=ceil(sqrt(n));
    w=(n-1)/size+1;
    for(int i=1;i<=n;++i) scanf("%d",a+i);
    setup();
    for(int i=n;i;--i)
    {
        int j=(i-1)/size+1;
        int len=inf;
        tour(i,j,len,i+1);
        for(++j;j<=w;++j)
        if(dis[a[i]][j]<len)
        tour(i,j,len,(j-1)*size+1);
    }
    printf("%d\n",DP());
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值