[BZOJ3550]-[ONTAK2010]Vacation-费用流

说在前面

昨天晚上看到这道题,思考了半个小时无果
今天早上来查题解,然而还是没看懂…
然后今天上午模拟测试考到原题了hhhh,考场现推一个小时


题目

BZOJ3550传送门

题目大意

给出3×N个数,现在需要从中选择一些数字。在满足「任意一个长为N的区间内被选中的数字不超过K个」的限制下,要求选的数字之和最大。询问这个和最大是多少

输入输出格式

输入格式:
第一行两个整数N,K,含义如题
接下来一行包含3*N个整数
保证N不超过200,K不超过10

输出格式:
输出一行一个整数表示答案


解法

这个题有两种建图方式,一种是网络流模型建图,另一种是线性规划建图,不过建出来的样子大同小异吧

网络流模型建图

考虑选择的数对区间的贡献,选择一个数,那么就会对包含这个位置的区间贡献1,如果每次选择的数字 所贡献的区间都不重复(即没有一个区间会在一次选择中 包含两个数字),那么我们选K次,这样就能在满足题目限制的情况下求出最大解。
具体建图:每个点向下一个点连边,费用0流量K,S向1连,3N向T连。1到2N号点,每个点向i+N号点连边,流量上限为1,费用为value[i](第i个数字的数值)。2N+1到3N号点,每个点向T连边,费用value[i],流量1。

线性规划建图

还不太会,还是不要听me口胡了,me把这一部分附在后面吧,可以去看一看GXZlegend的博客


下面是自带大常数的代码

考场上按照自己YY的不等式建的图,最好还是不要看hhh,免得看了头昏

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

int N , K , tp = 1 , head[605] , val[605] ;
int S , T , id_c , id[605] , ans ;
struct Path{
    int fr , pre , to , flow , fee ;
}p[200*6+2*200*2+1000] ;

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

bool inque[605] ;
int que[300005] , fr , ba , dis[605] , pre[605] ;
bool spfa(){
    memset( pre , 0 , sizeof( pre ) ) ;
    memset( dis , -1 , sizeof( dis ) ) ;
    fr = 1 , ba = 0 ; dis[S] = 0 ;
    que[++ba] = S ; inque[S] = true ;
    while( fr <= ba ){
        int u = que[fr++] ; 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 ){
                pre[v] = i ;//记录边编号
                dis[v] = dis[u] + p[i].fee ;
                if( !inque[v] ){
                    que[++ba] = v ;
                    inque[v] = true ;
                }
            }
        }
    } return dis[T] != -1 ;
}

int cnt ;
bool maxFlow(){
    cnt ++ ;
    if( !spfa() ) return false ;
    int u = T , flow = 20031109 ;
    while( u != S ){
        flow = min( flow , p[ pre[u] ].flow ) ;
        u = p[ pre[u] ].fr ;
    } u = T ;
    while( u != S ){
        ans += flow * p[ pre[u] ].fee ;
        p[ pre[u] ].flow -= flow ;
        p[ pre[u]^1 ].flow += flow ;
        u = p[ pre[u] ].fr ;
    }
    return flow && cnt != K ;
}

void solve(){
    for( int i = 1 ; i <= ( N << 1 ) ; i ++ )
        id[i] = ++id_c ;
    S = 0 , T = ++id_c ;
    for( int i = 1 ; i <= N ; i ++ ){
        In( S , id[i] , 1 , val[i] ) ;
        In( id[i] , S , 0 , -val[i] ) ;
        In( id[i] , id[i+N] , 1 , val[i+N] ) ;
        In( id[i+N] , id[i] , 0 , -val[i+N] ) ;
    }
    for( int i = N + 1 ; i <= ( N << 1 ) ; i ++ ){
        In( id[i] , T , 1 , val[i+N] ) ;
        In( T , id[i] , 0 , -val[i+N] ) ;
    }
    for( int i = 1 ; i <= id_c ; i ++ ){
        In( i-1 , i , K , 0 ) ;
        In( i , i-1 , 0 , 0 ) ;
    }
    while( maxFlow() ) ;
    printf( "%d\n" , ans ) ;
}

int main(){
    scanf( "%d%d" , &N , &K ) ;
    for( int i = 1 ; i <= 3 * N ; i ++ )
        scanf( "%d" , &val[i] ) ;
    solve() ;
}

附:

xi 表示第i个数字是否选中,选中为1,否则0。
根据题意,有:

x1+x2++xnKx2+x3++xn+1Kx2n+1+x2n+2++x3nK

现在全是小于等于式,不好看,于是我们在左边加上修正值 delta
x1+x2++xn+Δ1=Kx2+x3++xn+1+Δ2=Kx2n+1+x2n+2++x3n+Δ2n+1=K

相邻式相消,第一个式子和最后一个式子 与 0=0 相减
xn+1x1+Δ2Δ1=0xn+2x2+Δ3Δ2=0x3nx2n+Δ2n+1Δ2n=0x1+x2++xn+Δ1K=0x2n+1x2n+2x3nΔ2n+1+K=0

将上面的等式类比网络流中的两个等式:
流量守恒,即: Σf(i,v)Σf(u,i)=0 ,符号为正的是流出,而符号为负的是流入。
因此可以将上面的每一个等式看作网络流中的一个点, Δi 就是没有流满的部分,含有正号的项的等式 向对应的 含有负号该项的等式连边。对于常数项,如果带正号,相当于是流出到T,带负号相当于是从S流入。由于限制条件与x相关,因此给这些边加上费用即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值