说在前面
昨天晚上看到这道题,思考了半个小时无果
今天早上来查题解,然而还是没看懂…
然后今天上午模拟测试考到原题了hhhh,考场现推一个小时
题目
题目大意
给出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+⋯+xn≤Kx2+x3+⋯+xn+1≤K⋯⋯x2n+1+x2n+2+⋯+x3n≤K
现在全是小于等于式,不好看,于是我们在左边加上修正值 delta
⎧⎩⎨⎪⎪⎪⎪x1+x2+⋯+xn+Δ1=Kx2+x3+⋯+xn+1+Δ2=K⋯⋯x2n+1+x2n+2+⋯+x3n+Δ2n+1=K
相邻式相消,第一个式子和最后一个式子 与 0=0 相减
⎧⎩⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪xn+1−x1+Δ2−Δ1=0xn+2−x2+Δ3−Δ2=0⋯⋯x3n−x2n+Δ2n+1−Δ2n=0x1+x2+⋯+xn+Δ1−K=0−x2n+1−x2n+2−⋯−x3n−Δ2n+1+K=0
将上面的等式类比网络流中的两个等式:
流量守恒,即: Σf(i,v)−Σf(u,i)=0 ,符号为正的是流出,而符号为负的是流入。
因此可以将上面的每一个等式看作网络流中的一个点, Δi 就是没有流满的部分,含有正号的项的等式 向对应的 含有负号该项的等式连边。对于常数项,如果带正号,相当于是流出到T,带负号相当于是从S流入。由于限制条件与x相关,因此给这些边加上费用即可。