【typical】【JZOJ 5271】 神奇的救火现场

58 篇文章 0 订阅
28 篇文章 0 订阅

Description

这里写图片描述
这里写图片描述

Analysis

比较容易想到的是可撤销贪心的解法,老套路了
可能我脑回路清奇,或者是中了这题的毒,想的是另一个nlogn的做法,和那个链接的题的分析思路差不多
首先可以想到一个很simple的dp,排好序,从左到右,设 f[i][j] 表示第i个车匹配第j个栓的最小花费
显然存在一种最优解使得匹配不交叉,那么此处存在状态转移的单调右移性
f[i][j]=mink<jf[i1][k]+|a[i]b[j]| ,这个转移式是相当simple的,启示我们可以优化,且为了方便后面转移,我们只需要保存一个 g 表示f的前缀min
g 数组第二维想像成函数,这一定是一个单调递减函数,且右边最低点向右延伸成“平板”
考虑函数|a[i]b[j]|,由于 a,b 都是排好序单调的,这肯定是个单峰函数
那么考虑三分出那个极值点,那么极值点也是单增的,每次三分那个点肯定在平板上
两个函数的和长什么样?其实不重要,我们只需要知道新的 g <script type="math/tex" id="MathJax-Element-885">g</script>大致形状不发生改变,只是平板的高度可能会有所提升,平板的左端点可能右移
那么维护平板就好

Code

比赛的时候为了使程序鲁棒,我维护的是极值点附近±100个相邻位置的dp值

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,b,a) for(int i=b;i>=a;i--)
#define efo(i,v) for(int i=last[v];i;i=next[i])
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define mset(a,x) memset(a,x,sizeof(a))
using namespace std;
typedef long long ll;
const int N=1e5+5,T=200;
const ll INF=1e15;
int n,m,a[N],b[N],c[N];
ll f[N][T+4],g[5005][5005];
int main()
{
    freopen("fire.in","r",stdin);
    freopen("fire.out","w",stdout);
    scanf("%d %d",&n,&m);
    fo(i,1,n) scanf("%d",&a[i]);sort(a+1,a+n+1);
    fo(j,1,m) scanf("%d",&b[j]);sort(b+1,b+m+1);
    if(n<=5000 && m<=5000)
    {
        fo(i,1,m)
        {
            ll mn=g[i-1][i-1];
            fo(j,i,n+i-m)
            {
                g[i][j]=mn+abs(b[i]-a[j]);
                mn=min(mn,g[i-1][j]);
            }
        }
        ll ans=INF;
        fo(i,m,n) ans=min(ans,g[m][i]);
        printf("%lld",ans);
        return 0;
    }
    int lst=0;
    fo(i,1,m)
    {
        int l=i,r=n-m+i;
        while(l<r-1)
        {
            int m1=(l+r)>>1;int m2=(m1+r)>>1;
            if(abs(a[m1]-b[i])<abs(a[m2]-b[i])) r=m2;else l=m1;
        }
        while(l<n-m+i && abs(a[l+1]-b[i])<abs(a[l]-b[i])) l++;
        c[i]=max(c[i-1]+1,l-(T>>1));
    }
    fo(i,1,m)
    {
        int p1=-1;ll mn=INF;
        fo(j,0,T)
        {
            int k=j+c[i];
            while(p1<T && p1+1+c[i-1]<k) ++p1,mn=min(mn,f[i-1][p1]);
            f[i][j]=mn+abs(a[k]-b[i]);
        }
    }
    ll ans=INF;
    fo(i,0,T) ans=min(ans,f[m][i]);
    printf("%lld",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值