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

说在前面

之前还与Doggu探讨过如何理解线性规划
然而遇到这道题之后,me更加mengbi了…
感觉线性规划的题,果然还是要理解式子的含义,以及转化的正确性,而不是去理解建出来的图的含义啊=w=


题目

BZOJ1061传送门

题目大意

懒得写啦(~ ̄▽ ̄)~
去传送门看吧哈哈哈哈


解法

这个题就是说,通过花费代价,(可多次)选择一个区间使其+1,让最后这个数列中每个位置的值都大于该位置的限定值。
一开始看到这道题,me往最短路方面想了,因为这个题其实和差分约束的思路有点点像,要用最小的代价满足最苛刻的位置,然后无果。又往费用流方面想了想,然而没法实现区间分配流量,然后也没戏。

好了还是说正解吧
拿样例开刀,假设每个种类的志愿者招募人数是 x[i] x [ i ] ,那么应该满足下面这个
xi0x12x1+x23x2+x34 { x i ≥ 0 x 1 ≥ 2 x 1 + x 2 ≥ 3 x 2 + x 3 ≥ 4
在左边同时加上 Δi Δ i ,使式子变成等式
xi0Δi0x1=2+Δ1x1+x2=3+Δ2x2+x3=4+Δ3 { x i ≥ 0 Δ i ≥ 0 x 1 = 2 + Δ 1 x 1 + x 2 = 3 + Δ 2 x 2 + x 3 = 4 + Δ 3
然后在最前面和最后面都加上一个恒等式 0=0 0 = 0 ,用后式减前式,得到
xi0Δi0x1Δ12=0x2Δ2+Δ11=0x3x1+Δ2Δ31=0x3x2+Δ3+4=0 { x i ≥ 0 Δ i ≥ 0 x 1 − Δ 1 − 2 = 0 x 2 − Δ 2 + Δ 1 − 1 = 0 x 3 − x 1 + Δ 2 − Δ 3 − 1 = 0 − x 3 − x 2 + Δ 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[N] , 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() ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值