[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() ; }
相关文章推荐
- 【bzoj1061】[NOI2008]志愿者招募 线性规划与费用流
- [BZOJ 1061][Noi2008]志愿者招募:费用流|单纯型
- BZOJ.1061.[NOI2008]志愿者招募(线性规划 对偶原理 单纯形 / 费用流SPFA)
- [线性规划 费用流]BZOJ1061 志愿者招募 && BZOJ3112防守战线
- 【BZOJ1061】【NOI2008】志愿者招募 费用流神题、单纯形裸题(代码费用流)
- [无源汇上下界最小费用可行流 差分费用流] BZOJ 1061 [Noi2008]志愿者招募
- bzoj1061 [Noi2008]志愿者招募(费用流)
- BZOJ 1061 [Noi2008]志愿者招募(费用流)
- [BZOJ1061]NOI2008志愿者招募|费用流|线性规划
- [NOI2008][bzoj1061] 志愿者招募 [费用流+巧妙的建图]
- 【费用流】BZOJ1061[NOI2008]-志愿者招募
- bzoj1061-[Noi2008]志愿者招募-单纯形 & 费用流
- 【费用流|单纯形】BZOJ1061 [Noi2008]志愿者招募
- bzoj1061 [Noi2008]志愿者招募(网络流解决线性规划问题)
- [BZOJ1061][NOI2008]志愿者招募(费用流神题单纯形裸题)
- bzoj1061【NOI2008】志愿者招募
- BZOJ 1061 Noi2008 志愿者招募 单纯形
- 【NOI2008】bzoj1061 志愿者招募
- BZOJ 1061 Noi2008 志愿者招募 单纯形
- 【BZOJ】【1061】【NOI2008】志愿者招募