说在前面
dp凸优化真的是刷榜神器哈哈哈哈
权值log跑得飞快
这是CF上非ghost的第一页 emmmm
题目
codeforces Gym 101242传送门
BZOJ4609传送门
看题可戳传送门
解法
这题有一个比较简单的性质,找出来之后就是一个比较常见的决策单调性了
首先我们对正图和反图都跑一遍最短路,求得
到
的最短路
因为一个点的到
和
到该点的最短路都会经过分组大小
次,所以可以直接令每个点的权值为这两个距离的和
然后一个组的代价就是
组大小
组内权值和
然后现在的问题就是,如何把这些点分组。如果顺序是确定的,那么显然可以dp解决,然而这道题是无序的,我们尝试找一找最优策略。不难发现,我们把所有权值从大到小排序,然后再分组,这样是最优的
证明如下:
假设最优策略中,最大值所在组为
,
中最小值为
,最大的不在
中的点为
,设
所在集合为
- 如果 ,直接将 中随便一个点放入 ,显然新的解更优秀
- 接下来只考虑
:
- 如果 ,这就是我们假设的最优策略
- 如果 ,那么我们将 互换, 中代价将增加 ,而 中代价将减少 ,显然新解不会更差
证毕
所以我们得到两个结论,从大到小排序分组是最优的,并且组的大小越来越大
通过第二个性质可以直接缩减转移点,得到一个
的做法,代码极短
当然也可以用单调队列来实现
反正转化到这一步之后,就和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() ;
}