Description
Analysis
比较容易想到的是可撤销贪心的解法,老套路了
可能我脑回路清奇,或者是中了这题的毒,想的是另一个nlogn的做法,和那个链接的题的分析思路差不多
首先可以想到一个很simple的dp,排好序,从左到右,设
f[i][j]
表示第i个车匹配第j个栓的最小花费
显然存在一种最优解使得匹配不交叉,那么此处存在状态转移的单调右移性
f[i][j]=mink<jf[i−1][k]+|a[i]−b[j]|
,这个转移式是相当simple的,启示我们可以优化,且为了方便后面转移,我们只需要保存一个
g
表示
将
g
数组第二维想像成函数,这一定是一个单调递减函数,且右边最低点向右延伸成“平板”
考虑函数
那么考虑三分出那个极值点,那么极值点也是单增的,每次三分那个点肯定在平板上
两个函数的和长什么样?其实不重要,我们只需要知道新的
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;
}