您的位置:首页 > 其它

[BZOJ1061]-[Noi2008]志愿者招募-线性规划+费用流

2018-02-20 19:48 267 查看

说在前面

之前还与Doggu探讨过如何理解线性规划

然而遇到这道题之后,me更加mengbi了…

感觉线性规划的题,果然还是要理解式子的含义,以及转化的正确性,而不是去理解建出来的图的含义啊=w=

题目

BZOJ1061传送门

题目大意

懒得写啦(~ ̄▽ ̄)~

去传送门看吧哈哈哈哈

解法

这个题就是说,通过花费代价,(可多次)选择一个区间使其+1,让最后这个数列中每个位置的值都大于该位置的限定值。

一开始看到这道题,me往最短路方面想了,因为这个题其实和差分约束的思路有点点像,要用最小的代价满足最苛刻的位置,然后无果。又往费用流方面想了想,然而没法实现区间分配流量,然后也没戏。

好了还是说正解吧

拿样例开刀,假设每个种类的志愿者招募人数是x[i]x[i],那么应该满足下面这个

⎧⎩⎨⎪⎪⎪⎪xi≥0x1≥2x1+x2≥3x2+x3≥4{xi≥0x1≥2x1+x2≥3x2+x3≥4

在左边同时加上ΔiΔi,使式子变成等式

⎧⎩⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪xi≥0Δi≥0x1=2+Δ1x1+x2=3+Δ2x2+x3=4+Δ3{xi≥0Δi≥0x1=2+Δ1x1+x2=3+Δ2x2+x3=4+Δ3

然后在最前面和最后面都加上一个恒等式0=00=0,用后式减前式,得到

⎧⎩⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪xi≥0Δi≥0x1−Δ1−2=0x2−Δ2+Δ1−1=0x3−x1+Δ2−Δ3−1=0−x3−x2+Δ3+4=0{xi≥0Δi≥0x1−Δ1−2=0x2−Δ2+Δ1−1=0x3−x1+Δ2−Δ3−1=0−x3−x2+Δ3+4=0

发现其中所有的项,正负刚好各一次,符合网络流中的流量守恒

把每个等式都看成一个点,负项看作流入,正项看作流出。让有正项的等式连向有负项的等式,如果有正常数,则流出到T,负常数则从S流入。因为限制条件与x相关,于是与x有关的边都附上相应的费用,跑一遍最小费用流即可

关于这种做法的正确性,我们在化式子到建图的过程中,始终符合题目限制。也就是说,任何一组合法的解,都对应一种方案,而每一组解都对应了图中的一种最大流,我们需要找出合法方案中,花费最少的那个。因此费用流是正确的

这种方法的适用范围比较窄,只当所有变量都刚好出现正负各一次才能使用。这道题之所以可以,是因为每个人的工作时段都是一个区间,正项只会在区间始出现,负项只会在区间末出现

下面是自带大常数的代码

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

int N , M , tp = 1 , head[1005] , ned[1005] ;
int eq[1005] , S , T , id_c ;
struct Path{
int pre , to , flow , fee ;
}p[30005] ;

void In( int t1 , int t2 , int t3 , int t4 ){
if( !t3 ) return ;
p[++tp] = ( Path ){ head[t1] , t2 , t3 , t4 } ; head[t1] = tp ;
p[++tp] = ( Path ){ head[t2] , t1 , 0 , -t4 } ; head[t2] = tp ;
}

bool inque[1005] ;
deque<int> que ;
int dis[1005] , pre[1005] , preE[1005] ;
bool Spfa(){
memset( pre + 1 , 0 , id_c * sizeof( int ) ) ;
memset( dis + 1 , 0x3f , id_c * sizeof( int ) ) ;
dis[S] = 0 , que.push_back( S ) , inque[S] = true ;
while( !que.empty() ){
int u = que.front() ;
que.pop_front() ; inque[u] = false ;
for( int i = head[u] ; i ; i = p[i].pre ){
int v = p[i].to ;
if( dis[v] > dis[u] + p[i].fee && p[i].flow ){
dis[v] = dis[u] + p[i].fee ;
pre[v] = u , preE[v] = i ;
if( !inque[v] ){
if( !que.empty() && dis[v] <= dis[ que.front() ] )
que.push_front( v ) , inque[v] = true ;
else que.push_back( v ) , inque[v] = true ;
}
}
}
} return pre[T] ;
}

int addFlow(){
int flow = 0x3f3f3f3f , now = T , rt = 0 ;
while( now != S ){
flow = min( flow , p[ preE[now] ].flow ) ;
now = pre[now] ;
} now = T ;
while( now != S ){
p[ preE[now] ].flow -= flow ;
p[ preE[now]^1 ].flow += flow ;
rt += flow * p[ preE[now] ].fee ;
now = pre[now] ;
} return rt ;
}

void solve(){
int ans = 0 ;
while( Spfa() )
ans += addFlow() ;
printf( "%d" , ans ) ;
}

int main(){
scanf( "%d%d" , &N , &M ) ;
for( int i = 1 ; i <= N + 1 ; i ++ ) eq[i] = ++id_c ;
S = ++id_c , T = ++id_c ;

for( int i = 1 ; i <= N ; i ++ ){
scanf( "%d" , &ned[i] ) ;
if( ned[i] > ned[i-1] ) In( S , eq[i] , ned[i] - ned[i-1] , 0 ) ;
else In( eq[i] , T , ned[i-1] - ned[i] , 0 ) ;
In( eq[i+1] , eq[i] , 1e9 , 0 ) ;
}
In( eq[N+1] , T , ned
, 0 ) ;
for( int i = 1 , st , ed , c ; i <= M ; i ++ ){
scanf( "%d%d%d" , &st , &ed , &c ) ;
In( eq[st] , eq[ed+1] , 1e9 , c ) ;
}
solve() ;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: