说在前面
第一次写01分规,原来这么简单的嘛qwq
题目
题目大意
有N个有标号的白球和黑球,现在需要将这些球黑白两两配对。如果将球(i,j)配对,就会获得协和值a[i][j]和违和值b[i][j](这是两种属性)。现在询问一种配对方式,使得下式中的C最大。其中C是获得的协和值之和 与 违和值之和 的比值
输入输出格式
输入格式:
第一行一个整数N,含义如题
接下来N行,每行N个整数,表示a[i][j]
接下来N行,每行N个整数,表示b[i][j]
输出格式:
输出答案,保留六位小数
解法
关于01分规
这其实是一个很显然的01分数规划…
大概是这样的,要让这个式子
C=∑a∑b
C
=
∑
a
∑
b
中的
C
C
最大,其中a和b都是要么同时选并获得其值,要么同时不选。
假设目前已经取到了最优的C,那么就应该是这样:
把这个式子移项,得到
∑a−C∗∑b≤0
∑
a
−
C
∗
∑
b
≤
0
,发现这个式子是满足二分性的。什么意思呢?我们现在随便选一个数,把他当作C带入式子,经过计算求出
∑a−C∗∑b
∑
a
−
C
∗
∑
b
的最大值,如果为正,那么说明C还可以变大,否则C需要变小,二分答案即可。当然,计算最大值这个步骤依题而定,比如这道题就用的费用流。
但是上面的二分并没有充分的利用所有信息。既然已经计算出 ∑a−Cnow∗∑b ∑ a − C n o w ∗ ∑ b 的值,那么不妨就令 Cnext C n e x t 满足在当前决策情况下的 ∑a−Cnext∗∑b=0 ∑ a − C n e x t ∗ ∑ b = 0 ,多搞几次就能逼近最优解,效率比直接二分要高很多。不过,这种迭代只适用于可以较简单的求出 ∑a ∑ a 以及 ∑b ∑ b 的情形(一个很难求的例子:求一张图里的 costlen c o s t l e n 最大的环)。因此,用二分或是迭代都需要视情况而定,哪个简单写哪个
对于这道题
那么对于这道题呢,先随便给C定一个值,然后开始迭代。
确定C值之后就是一个最大费用最大流,S向每个白球连边,边权1费用0,白球和黑球连边,边权1费用a[i][j]-C*b[i][j],每个黑球向T连边,边权1费用0。
枚举每条边的流量来判断a[i][j]和b[i][j]有没有被选,从而算出
∑a
∑
a
和
∑b
∑
b
。当
Cnext
C
n
e
x
t
和
Cnow
C
n
o
w
的差值小于某个值的时候就终止
下面是自带大常数的代码
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
const double eps = 5 * 1e-8 ;
int N , a[105][105] , b[105][105] , tp , head[205] ;
int girl[105] , boy[105] , S , T , id_c ;
struct Path{
int pre , to , flow ;
double fee ;
}p[20005+1000] ;
int dcmp( double x ){
if( x > -eps && x < eps ) return 0 ;
return x > 0 ? 1 : -1 ;
}
void In( int t1 , int t2 , int t3 , double t4 ){
p[++tp] = ( Path ){ head[t1] , t2 , t3 , t4 } ; head[t1] = tp ;
p[++tp] = ( Path ){ head[t2] , t1 , 0 , -t4 } ; head[t2] = tp ;
}
deque<int> que ;
double dis[205] ;
bool inque[205] ;
int pre[205] , preE[205] ;
bool SPFA(){
for( int i = 1 ; i <= id_c ; i ++ ) dis[i] = -1e10 ;
memset( pre + 1 , 0 , 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( p[i].flow && dis[v] + 1e-10 < dis[u] + p[i].fee ){
dis[v] = dis[u] + p[i].fee ;
pre[v] = u , preE[v] = i ;
if( !inque[v] ){
if( !que.empty() && dis[v] > dis[ que.front() ] + 1e-10 )
que.push_front( v ) ;
else que.push_back( v ) ;
inque[v] = true ;
}
}
}
} return pre[T] ;
}
void addFlow(){
int now = T ;
while( now != S ){
p[ preE[now] ].flow -= 1 ;
p[ preE[now]^1 ].flow += 1 ;
now = pre[now] ;
}
}
void check( double x ){
tp = 1 ;
memset( head + 1 , 0 , id_c * sizeof( int ) ) ;
for( int i = 1 ; i <= N ; i ++ ){
In( S , girl[i] , 1 , 0 ) ;
In( boy[i] , T , 1 , 0 ) ;
}
for( int i = 1 ; i <= N ; i ++ )
for( int j = 1 ; j <= N ; j ++ )
In( girl[i] , boy[j] , 1 , 1.0 * a[i][j] - x * b[i][j] ) ;
while( SPFA() ) addFlow() ;
}
void solve(){
double ans , now = 1 ;
do{
ans = now ;
check( ans ) ;
double up = 0 , down = 0 ;
for( int i = 1 ; i <= N ; i ++ )
for( int j = head[ girl[i] ] , tmp = N ; j ; j = p[j].pre , tmp -- )
if( !p[j].flow ){
up += a[i][tmp] , down += b[i][tmp] ;
break ;
}
now = up / down ;
}while( dcmp( ans - now ) ) ;
printf( "%.6f" , ans ) ;
}
int main(){
scanf( "%d" , &N ) ;
for( int i = 1 ; i <= N ; i ++ )
girl[i] = ++id_c , boy[i] = ++id_c ;
S = ++id_c , T = ++id_c ;
for( int i = 1 ; i <= N ; i ++ )
for( int j = 1 ; j <= N ; j ++ )
scanf( "%d" , &a[i][j] ) ;
for( int i = 1 ; i <= N ; i ++ )
for( int j = 1 ; j <= N ; j ++ )
scanf( "%d" , &b[i][j] ) ;
solve() ;
}