[HackerRank]Hard Disk Drives/[JZOJ100005]Shoes

本文介绍了一种针对数轴上点移动问题的优化算法,旨在通过选择特定数量的关键点来最小化所有点移动距离之和。算法采用动态规划结合分治策略,并利用主席树进行高效查询。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

一个数轴上有n对点,第i对分别位于ai,bi
你需要在数轴上任意位置选择K个点,然后将所有点移动到这K个点上,原本在一对的点必须移动到同一个点上。
请你选择这K个点,最小化每个点移动距离之和。

2kn105,4n×K105,|ai|,|bi|109


题目分析

将每一对点看成数轴上的一个区间,分析发现,不管怎么移动点,两个点在移动过程中,要么一个点跨过该区间,要么两个点在区间内汇合,总之一对点的移动距离和至少是区间长度,至于出来的部分,如果区间内有关键点,那么就没有额外的消耗,否则一定是区间其中的一个端点与其距离最近的关键点的距离的两倍。
我们撇开区间本身造成的贡献(最后再加上去),考虑如何求上面所说的“多出来的部分”,为了方便计算我们先不将上面那个距离乘二,最后再乘上去。
于是现在我们的问题是,有若干个数轴上的区间,你要加入K个关键点,答案的计算如下:
对于一个区间,如果其内部有关键点,那么其对答案贡献为0
否则,我们找到距离其最近的关键点,其对答案贡献为这个区间到该关键点的距离
可以发现,一定存在一种最优解使得关键点都取值在数轴上原有的点。
考虑dp,令fi,j表示我们放入了i个关键点,当前做到了大小次序为j的原先点,(只考虑j以前开始的区间)的最小答案。
ω(i,j)表示在i放一个关键点,在j放一个关键点,[i,j]内的所有区间造成的答案,这个可以使用主席树在O(logn)的时间内查询。
dp方程如下:

fi,j=minkj{fi1,k+ω(k,j)}

这样做是O(n2Klogn)的,考虑怎么优化。
可以发现,令k为使fi,j达到最优的取值点,随着j的增大,k显然不会减小。
那么使用经典的决策单调性分治套路,每次计算出中点的取值点,然后分治两边,即可做到O(nKlog2n)
注意使用各种离散化来卡常。

代码实现

#include <algorithm>
#include <iostream>
#include <climits>
#include <cstdio>
#include <cctype>

using namespace std;

typedef long long LL;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
    while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}

const LL INF=LLONG_MAX/2;
const int N=100050;
const int X=N<<1;
const int LGN=17;
const int S=N*LGN;

struct chairman_tree
{
    int son[S][2],size[S];
    LL sum[S];
    int tot;

    int newnode(int rt)
    {
        sum[++tot]=sum[rt],size[tot]=size[rt];
        son[tot][0]=son[rt][0],son[tot][1]=son[rt][1];
        return tot;
    }

    void init(){tot=0;}

    void modify(int &rt,int rt0,int x,int l,int r,int delta)
    {
        rt=newnode(rt0);
        ++size[rt],sum[rt]+=delta;
        if (l==r) return;
        int mid=l+r>>1;
        if (x<=mid) modify(son[rt][0],son[rt0][0],x,l,mid,delta);
        else modify(son[rt][1],son[rt0][1],x,mid+1,r,delta);
    }

    void query(int rt,int rt0,int x,int l,int r,int &CNT,LL &SUM)
    {
        if (!x) return;
        if (!(size[rt]-size[rt0])) return;
        if (l==r)
        {
            if (x==l) CNT+=size[rt]-size[rt0],SUM+=sum[rt]-sum[rt0];
            return;
        }
        int mid=l+r>>1;
        if (x<=mid) query(son[rt][0],son[rt0][0],x,l,mid,CNT,SUM);
        else CNT+=size[son[rt][0]]-size[son[rt0][0]],SUM+=sum[son[rt][0]]-sum[son[rt0][0]],query(son[rt][1],son[rt0][1],x,mid+1,r,CNT,SUM);
    }
}t[2];

int a[N],b[N],newa[N],newb[N],news[N],xval[N],sval[N];
int root[X][2];
LL f[X],g[X];
int n,K,xtot,stot,xcnt,scnt;
LL ans;

struct data
{
    int key,id;
    bool tp;

    bool operator<(data const x)const{return key<x.key;}
}srt[X];

void pre()
{
    for (int i=1;i<=n;++i) srt[++stot].key=a[i]+b[i],srt[stot].id=i;
    sort(srt+1,srt+1+stot),srt[0].key=-2000000001,scnt=0;
    for (int i=1;i<=stot;++i) sval[news[srt[i].id]=scnt+=srt[i].key!=srt[i-1].key]=srt[i].key;
    for (int i=1;i<=n;++i)
    {
        srt[++xtot].key=a[i],srt[xtot].id=i,srt[xtot].tp=0;
        srt[++xtot].key=b[i],srt[xtot].id=i,srt[xtot].tp=1;
    }
    sort(srt+1,srt+1+xtot),srt[0].key=-1000000001,xcnt=0,xval[0]=-2000000001;
    t[0].init(),t[1].init();
    int lstl=0,lstr=0,lrtl=0,lrtr=0;
    for (int i=1;i<=xtot;++i)
    {
        xval[xcnt+=srt[i].key!=srt[i-1].key]=srt[i].key;
        if (srt[i].tp)
        {
            newb[srt[i].id]=xcnt;
            for (int j=lstr+1;j<xcnt;++j) root[j][1]=root[j-1][1];
            t[1].modify(root[xcnt][1],lrtr,news[srt[i].id],1,scnt,b[srt[i].id]);
            lstr=xcnt,lrtr=root[xcnt][1];
        }
        else
        {
            newa[srt[i].id]=xcnt;
            for (int j=lstl+1;j<xcnt;++j) root[j][0]=root[j-1][0];
            t[0].modify(root[xcnt][0],lrtl,news[srt[i].id],1,scnt,a[srt[i].id]);
            lstl=xcnt,lrtl=root[xcnt][0];
        }
    }
    for (int i=lstl+1;i<=xcnt;++i) root[i][0]=root[i-1][0];
    for (int i=lstr+1;i<=xcnt;++i) root[i][1]=root[i-1][1];
}

int search(int x)
{
    int ret=0,l=1,r=scnt;
    for (int mid;l<=r;)
    {
        mid=l+r>>1;
        if (sval[mid]<=x) l=(ret=mid)+1;
        else r=mid-1;
    }
    return ret;
}

void dp(int l,int r,int st,int en)
{
    if (l>r) return;
    int mid=l+r>>1,p;
    for (int i=st;i<=en&&i<=mid;++i)
    {
        int CNT;LL SUM,tmp=0;
        CNT=0,SUM=0,t[0].query(root[mid][0],i?root[i-1][0]:0,search(!i?INT_MIN:xval[i]+xval[mid]),1,scnt,CNT,SUM);
        tmp+=SUM-1ll*CNT*xval[i];
        CNT=0,SUM=0,t[1].query(root[mid][1],i?root[i-1][1]:0,search(!i?INT_MIN:xval[i]+xval[mid]),1,scnt,CNT,SUM);
        CNT=t[1].size[root[mid][1]]-t[1].size[i?root[i-1][1]:0]-CNT,SUM=t[1].sum[root[mid][1]]-t[1].sum[i?root[i-1][1]:0]-SUM;
        tmp+=1ll*CNT*xval[mid]-SUM+g[i];
        if (f[mid]>tmp) f[mid]=tmp,p=i;
    }
    dp(l,mid-1,st,p),dp(mid+1,r,p,en);
}

void calc()
{
    ans=INF;
    for (int i=1;i<=xcnt;++i) f[i]=INF;
    g[0]=0;
    for (int k=1;k<=K;++k)
    {
        for (int i=1;i<=xcnt;++i) g[i]=f[i],f[i]=INF;
        dp(1,xcnt,0,xcnt);
        int CNT=0;LL SUM=0;
        for (int i=xcnt,cur=xtot;i>=1;--i)
        {
            for (;cur&&srt[cur].key>=xval[i];--cur) if (!srt[cur].tp) ++CNT,SUM+=a[srt[cur].id];
            ans=min(ans,f[i]+SUM-1ll*CNT*xval[i]);
        }
    }
    ans*=2;
}

int main()
{
    freopen("shoes.in","r",stdin),freopen("shoes.out","w",stdout);
    n=read(),K=read();
    for (int i=1;i<=n;++i)
    {
        a[i]=read(),b[i]=read();
        if (a[i]>b[i]) swap(a[i],b[i]);
    }
    pre(),calc();
    for (int i=1;i<=n;++i) ans+=b[i]-a[i];
    printf("%lld\n",ans);
    fclose(stdin),fclose(stdout);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值