hicocoder#1933 : 最短管道距离2(DP+斜率优化)

时间限制:10000ms
单点时限:1000ms
内存限制:256MB
描述
在一张2D地图上有N座城市,坐标依次是(X1, Y1), (X2, Y2), … (XN, YN)。

现在H国要修建K条平行于X轴的天然气主管道。这些管道非常长,可以认为是一条条平行于X轴的直线。

小Ho想知道如何修建这K条管道,可以使N座城市到最近的管道的垂直距离之和最小。请你求出这个最小的距离之和。

输入
第一行包含2个整数N和K。

以下N行每行包含两个整数Xi, Yi。

1 <= N <= 10000

1 <= K <= 1000

0 <= Xi, Yi <= 1000000

输出
一个整数,代表最小的距离之和。

样例输入
4 2
0 0
0 100
100 0
100 100
样例输出
0

思路:可以想到将管道修在城市所在y坐标是满足答案的。

将城市按y坐标从小到大排序, s u m [ i ] sum[i] sum[i]代表前 i i i个城市的y坐标之和, d [ i ] [ j ] d[i][j] d[i][j]表示前 i i i个城市共修建了 j j j个管道且第 i i i个城市处修建了一个管道的最小距离; f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i个城市共修建了 j j j个管道(第 i i i个城市处可能没有修建管道)的最小距离。

则转移方程:
d [ i ] [ j ] = f [ k ] [ j − 1 ] + ( i − k ) ∗ y [ i ] − ( s u m [ i ] − s u m [ k ] ) d[i][j]=f[k][j-1]+(i-k)*y[i]-(sum[i]-sum[k]) d[i][j]=f[k][j1]+(ik)y[i](sum[i]sum[k]) ( [ k + 1 , i ] 之 间 的 城 市 选 择 i 作 为 最 近 的 管 道 [k+1,i]之间的城市选择i作为最近的管道 [k+1,i]i)

f [ i ] [ j ] = d [ k ] [ j ] + s u m [ i ] − s u m [ k ] − ( i − k ) ∗ y [ k ] f[i][j]=d[k][j]+sum[i]-sum[k]-(i-k)*y[k] f[i][j]=d[k][j]+sum[i]sum[k](ik)y[k] ( [ k + 1 , i ] 之 间 的 城 市 选 择 k 作 为 最 近 的 管 道 [k+1,i]之间的城市选择k作为最近的管道 [k+1,i]k)

转化后即为: f [ k ] [ j − 1 ] + s u m [ k ] = k ∗ y [ i ] + s u m [ i ] − i ∗ y [ i ] + d [ i ] [ j ] f[k][j-1]+sum[k]=k*y[i]+sum[i]-i*y[i]+d[i][j] f[k][j1]+sum[k]=ky[i]+sum[i]iy[i]+d[i][j] d [ k ] [ j ] − s u m [ k ] + k ∗ y [ k ] = i ∗ y [ k ] − s u m [ i ] + f [ i ] [ j ] d[k][j]-sum[k]+k*y[k]=i*y[k]-sum[i]+f[i][j] d[k][j]sum[k]+ky[k]=iy[k]sum[i]+f[i][j]

这样就可以用斜率优化来进行DP转移了。

#include<bits/stdc++.h>
using namespace std;
const int MOD=1e9+7;
const int MAX=1e4+10;
typedef long long ll;
ll sum[MAX];
struct Point
{
    ll x,y;
}p[MAX];
int cmp(const Point&A,const Point &B){return A.y<B.y;}
ll d[MAX][1010];
ll f[MAX][1010];
ll X[MAX],Y[MAX];
int main()
{
    int n,m;
    cin>>n>>m;
    for(ll i=1;i<=n;i++)scanf("%lld%lld",&p[i].x,&p[i].y);
    sort(p+1,p+n+1,cmp);
    for(ll i=1;i<=n;i++)sum[i]=sum[i-1]+p[i].y;
    for(ll i=1;i<=n;i++)d[i][1]=p[i].y*i-sum[i];
    ll L,R;
    for(ll j=1;j<=m;j++)
    {
        L=0,R=0;
        for(ll i=1;i<=n;i++)
        {
            while(L<R&&Y[L+1]-X[L+1]*i<=Y[L]-X[L]*i)L++;
            f[i][j]=min(d[i][j],Y[L]-i*X[L]+sum[i]);
            ll y=d[i][j]-sum[i]+i*p[i].y;
            ll x=p[i].y;
            while(L<R&&(Y[R]-Y[R-1])*(x-X[R-1])>=(y-Y[R-1])*(X[R]-X[R-1]))R--;
            X[R+1]=x;
            Y[R+1]=y;
            R++;
        }
        L=0,R=0;
        for(ll i=1;i<=n;i++)
        {
            while(L<R&&Y[L+1]-X[L+1]*p[i].y<=Y[L]-X[L]*p[i].y)L++;
            d[i][j+1]=Y[L]-p[i].y*X[L]-sum[i]+p[i].y*i;
            ll y=f[i][j]+sum[i];
            ll x=i;
            while(L<R&&(Y[R]-Y[R-1])*(x-X[R-1])>=(y-Y[R-1])*(X[R]-X[R-1]))R--;
            X[R+1]=x;
            Y[R+1]=y;
            R++;
        }
    }
    cout<<f[n][m]<<endl;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值