100005. Shoes

28 篇文章 0 订阅
17 篇文章 0 订阅

题目大意

给定 n 个排在数轴上的点对(ai,bi),现在要确定 k 个终点,使得每个点到其中任意一个点的距离和最小。对于同一对的点,必须到同一个终点。

Data Constraint
n,k105,n×k105

题解

对于一对点 (ai,bi) 显然将终点设在 ai+bi2 最优,所以先将所有点对按照 ai+bi2 排序,那么就能保证存在一种最优方案,可以将点对划分成 k 个连续区间,同一个区间内的点对都到同一个终点。
然后就可以设状态了fi,j表示已经确定了 i 个终点,前j对点对都已经找到了终点。
转移显然: fi,j=min{fi1,k+mincost(k+1,j)} mincost(i,j) 表示第 i 对到第j对点到同一个终点的最小距离和。注意到这个转移是满足决策单调性的,所以可以分治转移。现在的问题是如何快速计算 mincost(i,j)
注意到一个结论,对于一些点对,将终点设在所有点最中间的那个一定是一种最优解。
所以就可以用一个可持久化线段树来维护,每次在主席树上二分出一个最中间的点,然后计算即可。

时间复杂度: O(nklog2n)

SRC

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std ;

#define N 100000 + 10
typedef long long ll ;
const int MINV = -1e9 ;
const int MAXV = 1e9 ;
struct Segment {
    int a , b ;
} Seg[N] ;
struct Tree {
    int Son[2] ;
    ll Sum ;
    int Num ;
} T[40*N] ;

int Root[N] ;
ll f[2][N] , g[N] ;
int n , m , now , Cnt ;
ll retnum , retsum ;

bool cmp ( Segment x , Segment y ) { return x.a + x.b < y.a + y.b ; }

ll sabs( ll x ) { return x < 0 ? -x : x ; }

int Calc( int x ) {
    int ret = x / 2 ;
    if ( x < 0 && x % 2 != 0 ) ret -- ;
    return ret ;
}

int NewNode( int last ) {
    ++ Cnt ;
    T[Cnt] = T[last] ;
    return Cnt ;
}

void Insert( int v , int l , int r , int x ) {
    if ( l == r ) {
        T[v].Sum += x ;
        T[v].Num ++ ;
        return ;
    }
    int mid = Calc( l + r ) ;
    if ( x <= mid ) {
        T[v].Son[0] = NewNode( T[v].Son[0] ) ;
        Insert( T[v].Son[0] , l , mid , x ) ;
    } else {
        T[v].Son[1] = NewNode( T[v].Son[1] ) ;
        Insert( T[v].Son[1] , mid + 1 , r , x ) ;
    }
    T[v].Sum = T[T[v].Son[0]].Sum + T[T[v].Son[1]].Sum ;
    T[v].Num = T[T[v].Son[0]].Num + T[T[v].Son[1]].Num ;
}

int Search( int lv , int rv , int l , int r , int rank ) {
    if ( l == r ) return l ;
    int mid = Calc( l + r ) ;
    int left = T[T[rv].Son[0]].Num - T[T[lv].Son[0]].Num ;
    if ( left > rank ) return Search( T[lv].Son[0] , T[rv].Son[0] , l , mid , rank ) ;
    return Search( T[lv].Son[1] , T[rv].Son[1] , mid + 1 , r , rank - left ) ;
}

void Find( int lv , int rv , int l , int r , int x , int y ) {
    if ( T[rv].Num - T[lv].Num == 0 ) return ;
    if ( l == x && r == y ) {
        retnum += T[rv].Num - T[lv].Num ;
        retsum += T[rv].Sum - T[lv].Sum ;
        return ;
    }
    int mid = Calc( l + r ) ;
    if ( y <= mid ) Find( T[lv].Son[0] , T[rv].Son[0] , l , mid , x , y ) ;
    else if ( x > mid ) Find( T[lv].Son[1] , T[rv].Son[1] , mid + 1 , r , x , y ) ;
    else {
        Find( T[lv].Son[0] , T[rv].Son[0] , l , mid , x , mid ) ;
        Find( T[lv].Son[1] , T[rv].Son[1] , mid + 1 , r , mid + 1 , y ) ;
    }
}

ll Cost( int l , int r ) {
    ll ret = 0 ;
    int w = Search( Root[l-1] , Root[r] , MINV , MAXV , r - l + 1 ) ;
    retnum = retsum = 0 ;
    Find( Root[l-1] , Root[r] , MINV , MAXV , MINV , w - 1 ) ;
    ret += retnum * w - retsum ;
    retnum = retsum = 0 ;
    Find( Root[l-1] , Root[r] , MINV , MAXV , w + 1 , MAXV ) ;
    ret += retsum - retnum * w ;
    return ret ;
}

void DIV( int l1 , int r1 , int l2 , int r2 ) {
    if ( l1 > r1 ) return ;
    int mid = (l1 + r1) >> 1 ;
    g[mid] = 1e18 ;
    int wz = 0 ;
    for (int i = l2 ; i <= r2 ; i ++ ) {
        if ( i > mid + 1 ) break ;
        ll cost = Cost( i , mid ) ;
        if ( f[now][i-1] + cost < g[mid] ) {
            g[mid] = f[now][i-1] + cost ;
            wz = i ;
        }
    }
    if ( l1 == r1 ) return ;
    DIV( l1 , mid - 1 , l2 , wz ) ;
    DIV( mid + 1 , r1 , wz , r2 ) ;
}

int main() {
    freopen( "shoes.in" , "r" , stdin ) ;
    freopen( "shoes.out" , "w" , stdout ) ;
    scanf( "%d%d" , &n , &m ) ;
    for (int i = 1 ; i <= n ; i ++ ) {
        scanf( "%d%d" , &Seg[i].a , &Seg[i].b ) ;
        if ( Seg[i].a > Seg[i].b ) swap( Seg[i].a , Seg[i].b ) ;
    }
    sort( Seg + 1 , Seg + n + 1 , cmp ) ;
    Root[0] = ++ Cnt ;
    for (int i = 1 ; i <= n ; i ++ ) {
        Root[i] = NewNode( Root[i-1] ) ;
        Insert( Root[i] , MINV , MAXV , Seg[i].a ) ;
        Insert( Root[i] , MINV , MAXV , Seg[i].b ) ;
    }
    memset( f , 63 , sizeof(f) ) ;
    f[0][0] = 0 ;
    Cost( 1 , 2 ) ;
    for (int i = 1 ; i <= m ; i ++ ) {
        DIV( 0 , n , 1 , n ) ;
        for (int j = 0 ; j <= n ; j ++ ) f[!now][j] = g[j] ;
        now ^= 1 ;
    }
    printf( "%lld\n" , f[now][n] ) ;
    return 0 ;
}

以上.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值