1. 程式人生 > >【bzoj4819】[Sdoi2017]新生舞會 分數規劃+費用流

【bzoj4819】[Sdoi2017]新生舞會 分數規劃+費用流

pac 位置 str cpp 題目 手動 經驗 進一步 sof

題目描述

學校組織了一次新生舞會,Cathy作為經驗豐富的老學姐,負責為同學們安排舞伴。有n個男生和n個女生參加舞會買一個男生和一個女生一起跳舞,互為舞伴。Cathy收集了這些同學之間的關系,比如兩個人之前認識沒計算得出 a[i][j] ,表示第i個男生和第j個女生一起跳舞時他們的喜悅程度。Cathy還需要考慮兩個人一起跳舞是否方便,比如身高體重差別會不會太大,計算得出 b[i][j],表示第i個男生和第j個女生一起跳舞時的不協調程度。當然,還需要考慮很多其他問題。Cathy想先用一個程序通過a[i][j]和b[i][j]求出一種方案,再手動對方案進行微調。Cathy找到你,希望你幫她寫那個程序。一個方案中有n對舞伴,假設沒對舞伴的喜悅程度分別是a‘1,a‘2,...,a‘n,假設每對舞伴的不協調程度分別是b‘1,b‘2,...,b‘n。令C=(a‘1+a‘2+...+a‘n)/(b‘1+b‘2+...+b‘n),Cathy希望C值最大。

輸入

第一行一個整數n。 接下來n行,每行n個整數,第i行第j個數表示a[i][j]。 接下來n行,每行n個整數,第i行第j個數表示b[i][j]。 1<=n<=100,1<=a[i][j],b[i][j]<=10^4

輸出

一行一個數,表示C的最大值。四舍五入保留6位小數,選手輸出的小數需要與標準輸出相等

樣例輸入

3
19 17 16
25 24 23
35 36 31
9 5 6
3 4 2
7 8 9

樣例輸出

5.357143


題解

分數規劃+費用流

二分答案mid,將每個點的值看作a-b*mid。

由於每個男生只能搭配一名不同的女生,所以問題可以轉化為:1個n*n的矩陣中每個位置都有1個數,求選出n個彼此不在同一行或同一列的數的和的最大值是多少。

加邊s->i(1,0),i+n->t(1,0),i->j+n(1,v[i][j]),跑最大費用最大流,若大於0則調整下界,否則調整上界,直至上下界基本重合。

證明:如果最大費用大於0,則∑(v[ik][jk])>0,即∑(a[ik][jk]-b[ik][jk]*mid)>0,即∑a[ik][jk]>∑b[ik][jk]*mid,即∑a[ik][jk]/∑b[ik][jk]>mid,故需要調整下界來進一步更新答案,否則調整上界來調整答案。

#include <cstdio>
#include <cstring>
#include <queue>
#define eps 1e-7
using namespace std;
queue<int> q;
int n , head[210] , to[30000] , val[30000] , next[30000] , cnt , s , t , from[210] , pre[210];
double a[110][110] , b[110][110] , cost[30000] , dis[210];
void add(int x , int y , int v , double c)
{
    to[++cnt] = y , val[cnt] = v , cost[cnt] = c , next[cnt] = head[x] , head[x] = cnt;
    to[++cnt] = x , val[cnt] = 0 , cost[cnt] = -c , next[cnt] = head[y] , head[y] = cnt;
}
bool spfa()
{
    int x , i;
    for(i = s ; i <= t ; i ++ ) from[i] = -1 , dis[i] = 10000000.0;
    dis[s] = 0 , q.push(s);
    while(!q.empty())
    {
        x = q.front() , q.pop();
        for(i = head[x] ; i ; i = next[i])
            if(val[i] && dis[to[i]] > dis[x] + cost[i])
                dis[to[i]] = dis[x] + cost[i] , from[to[i]] = x , pre[to[i]] = i , q.push(to[i]);
    }
    return ~from[t];
}
double mincost()
{
    int k , i;
    double ans = 0;
    while(spfa())
    {
        k = 0x3f3f3f3f;
        for(i = t ; i != s ; i = from[i]) k = min(k , val[pre[i]]);
        ans += k * dis[t];
        for(i = t ; i != s ; i = from[i]) val[pre[i]] -= k , val[pre[i] ^ 1] += k;
    }
    return ans;
}
bool judge(double mid)
{
    int i , j;
    memset(head , 0 , sizeof(head));
    cnt = 1;
    for(i = 1 ; i <= n ; i ++ ) add(s , i , 1 , 0);
    for(i = 1 ; i <= n ; i ++ ) add(i + n , t , 1 , 0);
    for(i = 1 ; i <= n ; i ++ )
        for(j = 1 ; j <= n ; j ++ )
            add(i , j + n , 1 , mid * b[i][j] - a[i][j]);
    return mincost() < 0;
}
int main()
{
    int i , j;
    double l = 0.0 , r = 10000000.0 , mid;
    scanf("%d" , &n);
    s = 0 , t = 2 * n + 1;
    for(i = 1 ; i <= n ; i ++ ) for(j = 1 ; j <= n ; j ++ ) scanf("%lf" , &a[i][j]);
    for(i = 1 ; i <= n ; i ++ ) for(j = 1 ; j <= n ; j ++ ) scanf("%lf" , &b[i][j]);
    while(l <= r)
    {
        mid = (l + r) / 2;
        if(judge(mid)) l = mid + eps;
        else r = mid - eps;
    }
    printf("%.6lf\n" , (l + r) / 2);
    return 0;
}

【bzoj4819】[Sdoi2017]新生舞會 分數規劃+費用流