[BZOJ4609]-[Wf2016]Branch Assignment-dp凸优化

说在前面

dp凸优化真的是刷榜神器哈哈哈哈
权值log跑得飞快
这里写图片描述
这是CF上非ghost的第一页 emmmm


题目

codeforces Gym 101242传送门
BZOJ4609传送门
看题可戳传送门


解法

这题有一个比较简单的性质,找出来之后就是一个比较常见的决策单调性了

首先我们对正图和反图都跑一遍最短路,求得 1B 1 ⋯ B B+1 B + 1 的最短路
因为一个点的到 B+1 B + 1 B+1 B + 1 到该点的最短路都会经过分组大小 1 − 1 次,所以可以直接令每个点的权值为这两个距离的和
然后一个组的代价就是 ( ( 组大小1)(组内权值和 ) )

然后现在的问题就是,如何把这些点分组。如果顺序是确定的,那么显然可以dp解决,然而这道题是无序的,我们尝试找一找最优策略。不难发现,我们把所有权值从大到小排序,然后再分组,这样是最优的

证明如下:
假设最优策略中,最大值所在组为P P P 中最小值为i,最大的不在 P P 中的点为j,设 j j 所在集合为Q

  • 如果 |P|>|Q| | P | > | Q | ,直接将 P P 中随便一个点放入 Q ,显然新的解更优秀
  • 接下来只考虑 |P||Q| | P | ≤ | Q |
    • 如果 ij i ≥ j ,这就是我们假设的最优策略
    • 如果 i<j i < j ,那么我们将 i,j i , j 互换, P P 中代价将增加 (j1)(|P|1),而 Q Q 中代价将减少 (j1)(|Q|1),显然新解不会更差

证毕

所以我们得到两个结论,从大到小排序分组是最优的,并且组的大小越来越大
通过第二个性质可以直接缩减转移点,得到一个 n2log2n n 2 log 2 ⁡ n 的做法,代码极短
当然也可以用单调队列来实现 n2log2n n 2 log 2 ⁡ n
反正转化到这一步之后,就和codeforces321E差不多了,直接做就ok


下面是代码

#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <functional>
using namespace std ;

int N , B , S , R ;
struct Path{ int pre , to , len ; } ;
struct dijk_Data{
    int dis , id ;
    dijk_Data( int _ , int __ ):dis(_) , id(__) {} ;
    bool operator < ( const dijk_Data &A ) const {
        return dis > A.dis ;
    }
} ;
struct Data{
    int k , ed ;
    long long val ;
} dp[5005] , que[5005] ;
struct Graph{
    int head[5005] , tp , dist[5005] ;
    Path p[50005] ;
    void In( int t1 , int t2 , int t3 ){
        p[++tp] = ( Path ){ head[t1] , t2 , t3 } ; head[t1] = tp ;
    }

    bool vis[5005] ;
    priority_queue<dijk_Data> que ;
    void dijk(){
        memset( dist , 0x3f , sizeof( dist ) ) ;
        dist[B+1] = 0 , que.push( dijk_Data( 0 , B + 1 ) ) ;
        while( !que.empty() ){
            int u = que.top().id ; que.pop() ;
            if( vis[u] ) continue ; vis[u] = true ;
            for( int i = head[u] ; i ; i = p[i].pre ){
                int v = p[i].to ;
                if( dist[v] > dist[u] + p[i].len ){
                    dist[v] = dist[u] + p[i].len ;
                    que.push( dijk_Data( dist[v] , v ) ) ;
                }
            }
        }
    }
} G1 , G2 ;

long long sum[5005] ;
void preWork(){
    G1.dijk() , G2.dijk() ;
    for( int i = 1 ; i <= B ; i ++ ) sum[i] = G1.dist[i] + G2.dist[i] ;
    sort( sum + 1 , sum + B + 1 , greater<long long>() ) ;
    for( int i = 1 ; i <= B ; i ++ ) sum[i] += sum[i-1] ;
}

int fr , ba ;
long long Val( Data x , int now ){
    return x.val + ( sum[now] - sum[x.ed] ) * ( now - x.ed - 1 ) ;
}

int better( Data x , Data y ){
    int lf = x.ed , rg = B , rt = B + 1 ;
    while( lf <= rg ){
        int mid = ( lf + rg ) >> 1 ;
        long long v1 = Val( x , mid ) , v2 = Val( y , mid ) ;
        if( v1 < v2 || ( v1 == v2 && x.k < y.k ) ) rg = mid - 1 , rt = mid ;
        else lf = mid + 1 ;
    } return rt ;
}

void Push( Data x ){
    while( ba > fr ){
        int t1 = better( que[ba] , que[ba-1] ) , t2 = better( x , que[ba] ) ;
        if( t1 > t2 || ( t1 == t2 && que[ba].k >= x.k ) ) ba -- ;
        else break ;
    } que[++ba] = x ;
}

void Pop( int i ){
    while( ba > fr ){
        long long v1 = Val( que[fr] , i ) , v2 = Val( que[fr+1] , i ) ;
        if( v1 > v2 || ( v1 == v2 && que[fr].k > que[fr+1].k ) ) fr ++ ;
        else break ;
    }
}

Data calc( long long mid ){
    fr = 1 , ba = 0 , que[++ba] = ( Data ){ 0 , 0 , 0 } ;
    for( int i = 1 ; i <= B ; i ++ ){
        Pop( i ) ;
        dp[i] = ( Data ){ que[fr].k + 1 , i , Val( que[fr] , i ) + mid } ;
        Push( dp[i] ) ;
    } return dp[B] ;
}

void solve(){
    long long lf = 0 , rg = sum[B] * B , ans ;
    while( lf <= rg ){
        long long mid = ( lf + rg ) >> 1 ;
        Data res = calc( mid ) ;
        if( res.k == S )
            return ( void )printf( "%lld\n" , res.val - mid * S ) ;
        if( res.k > S ) lf = mid + 1 ;
        else ans = mid ,rg = mid - 1 ;
    } printf( "%lld\n" , calc( ans ).val - ans * S ) ;
}

int main(){
    scanf( "%d%d%d%d" , &N , &B , &S , &R ) ;
    for( int i = 1 , u , v , l ; i <= R ; i ++ ){
        scanf( "%d%d%d" , &u , &v , &l ) ;
        G1.In( u , v , l ) , G2.In( v , u , l ) ;
    } preWork() ; solve() ;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值