题解:SDOI2017 新生舞会

Description

学校组织了一次新生舞会,Cathy 作为经验丰富的老学姐,负责为同学们安排舞伴。

有 \(n\) 个男生和 \(n\) 个女生参加舞会。一个男生和一个女生一起跳舞,互为舞伴。

Cathy 收集了这些同学之间的关系,比如两个人之前认不认识,计算得出 \(a_{i,j}\) ,表示第 \(i\) 个男生和第 \(j\) 个女生一起跳舞获得的愉快程度。

Cathy 还需要考虑两个人一起跳舞是否方便,比如身高体重差别会不会太大,计算得出 \(b_{i,j}\) ,表示第 \(i\) 个男生和第 \(j\) 个女生一起跳舞时的不协调程度。

一个方案中有 \(n\) 对舞伴,假设每对舞伴的愉快程度分别为 \(a'_1,a'_2,\dots,a'_n\) ,不协调程度分别为 \(b'_1,b'_2,\dots,b'_n\) ,

我们令

\[\large
C = \frac{\sum_{i = 1}^{n} a'_i}{\sum_{i = 1}^{n} b'_i}
\]

Cathy 希望方案的 C 值最大。

当然,还需要考虑许多其他问题。

Cathy 想先用一个程序通过 \(a_{i,j}\) 和 \(b_{i,j}\) 求出一种方案,再手动对方案进行微调。

Cathy 找到你,希望你帮她写那个程序。

\(1 \leq n \leq 100, 1 \leq a_{i,j},b_{i,j} \leq 10^4\)

Algorithm

看见题目给了个 \(\large C = \frac{\sum_{i = 1}^{n} a'_i}{\sum_{i = 1}^{n} b'_i}\) 的式子,阅题无数的同学一定就知道怎么做了:分数规划嘛。

题目要求最优化地安排一种方案,使得给定的描述方案性价比的比例式取得最值。

除非你能证明某种贪心策略的正确性,否则从正面考虑这样的问题是极端困难的。

01分数规划的思路则是从反面入手,我们二分答案。

我们二分最终的比值 \(C\) ,如果存在某种方案的比值更优,则存在:

\[\begin{align*}
&\frac{\sum_{i = 1}^{n} a'_i}{\sum_{i = 1}^{n} b'_i} > C \\
\Rightarrow &\sum_{i = 1}^{n} a'_i > C \cdot \sum_{i = 1}^{n} b'_i \\
\Rightarrow &\sum_{i = 1}^{n} (C \cdot b'_i - a'_i) < 0
\end{align*}
\]

反之同理。

于是问题变成了不断验证是否存在 \(\sum_{i = 1}^{n} (C \cdot b'_i - a'_i) > 0\) 继而不断缩小 \(C\) 的可能取值范围。

换言之,如果我们实现了一个 \(check(c)\) 函数能实现对应功能的话,就只需要这样写:

double l = 0, r = 1e6, mid;
while(r - l > eps)
{
mid = (l + r) / 2;
if(check(mid) < 0) l = mid;
else r = mid;
}

我以为分数规划是一个令人心潮澎湃的算法。它既有理性的色彩,又极富暴力的美感,而且简单得惊人。

接下来考虑如何实现这个 \(check(c)\) 。

先把题面上那个 \(a'_i,b'_i\) 的一撇的扒掉。

现在问题本质上是给定一个矩阵 \(c\) ,满足 \(c_{i,j} = (C \cdot b_{i,j} - a_{i,j})\) ,

要求要在矩阵中选出 \(n\) 个数字,满足不存在任意两个选中的数组在同一行或同一列。

怎么做呢?

  1. 我会暴力!\(O(n!)\) 全排列!

    ……那还分数规划干啥

  2. 我会状压 DP !用 \(dp_{i,j}\) 表示现在考虑到第 \(i\) 行,所有列是否已经取数的状态压缩成数字 \(j\) 。

    ……复杂度 \(O(n^2 \cdot 2^n \cdot log 1e6)\) ,大概能过 \(40\%\) ?

  3. 我会费用流!

    可以发现问题本质上是个男女匹配问题,于是考虑建立费用流模型。

    考虑样例

    \[a=\begin{bmatrix}
    19 & 17 & 16 \\
    25 & 24 & 23 \\
    35 & 36 & 31 \\
    \end{bmatrix}
    ,~~
    b = \begin{bmatrix}
    9 & 5 & 6 \\
    3 & 4 & 2 \\
    7 & 8 & 9 \\
    \end{bmatrix}
    \]

    假如我们要验证 \(C = 1\) 的情况,那么有

    \[c = \begin{bmatrix}
    10 & 12 & 10 \\
    22 & 20 & 21 \\
    28 & 28 & 22 \\
    \end{bmatrix}
    \]

    建立这样一个图(本来想标注权值的,但是太糊了还是算了吧):

    令图上所有边的流量上限都是 1 ,这就保证了最大流只能跑过图中的一些匹配。

    令图中 \(M_i \to F_j\) 的边权为 \(c_{i,j}\) ,与 \(s,t\) 连接的所有边权都为 0,那么我们需要验证的值就是跑最大流所经边权之和的最小值,也就是最小费用最大流。

    有关最小费用最大流的实现,我是使用 \(Dijkstra\) 配合势能函数魔改dinic的版本。

    代码:

    #include<bits/stdc++.h>
    using namespace std; const double eps = 1e-7;
    template<const int N, const int M>
    class Graph {
    private:
    typedef pair<double, int> Node;
    priority_queue<Node> que;
    const double INF = 1e7;
    int beg[N], nex[M], tar[M], cap[M], ite[N], len;
    double pot[N], dis[N], cst[M];
    bool vis[N]; public:
    int n, s, t;
    inline void clear() {
    memset(pot, 0, sizeof(pot));
    memset(beg, 0, sizeof(beg));
    memset(nex, 0, sizeof(nex));
    len = 1;
    }
    Graph() {
    clear();
    }
    inline void add_edge(int a, int b, int c, double d)
    {
    ++len, tar[len] = b, cap[len] = c, cst[len] = d;
    nex[len] = beg[a], beg[a] = len;
    } inline void add_pipe(int a, int b, int c, double d)
    {
    add_edge(a, b, c, +d);
    add_edge(b, a, 0, -d);
    } inline bool dijkstra(int s, int t)
    {
    fill(dis, dis + n + 1, INF);
    que.push(Node(dis[s] = 0, s)); while(!que.empty())
    {
    Node cur = que.top(); que.pop();
    int u = cur.second;
    if(-cur.first > dis[u]) continue;
    for(int i = beg[u]; i; i = nex[i])
    {
    int v = tar[i];
    double tmp = dis[u] + cst[i] + pot[u] - pot[v];
    if(cap[i] && dis[v]- tmp > eps)
    que.push(Node(-(dis[v] = tmp), v));
    }
    }
    return dis[t] < INF;
    } int dfs(int u, int flo)
    {
    if(u == t) return flo; int rst = flo;
    vis[u] = true;
    for(int &i = ite[u]; i; i = nex[i])
    {
    int v = tar[i];
    if(vis[v] || !cap[i]) continue;
    double tmp = dis[u] + cst[i] + pot[u] - pot[v];
    if(fabs(tmp - dis[v]) < eps)
    {
    int res = dfs(v, min(rst, cap[i]));
    rst -= res;
    cap[i] -= res;
    cap[i ^ 1] += res;
    }
    }
    vis[u] = false;
    return flo - rst;
    } inline Node costflow()
    {
    Node ret = Node(0, 0);
    while(dijkstra(s, t))
    {
    memcpy(ite, beg, sizeof(ite));
    int res = dfs(s, INF);
    for(int i = 1; i <= n; ++i)
    if(dis[i] < INF) pot[i] += dis[i];
    ret.first += res * pot[t];
    ret.second += res;
    }
    return ret;
    } }; template<class T>
    inline void read(T &x)
    {
    char c = getchar(); x = 0;
    while(c < '0' || '9' < c) c = getchar();
    while('0' <= c && c <= '9')
    {
    x = (x << 1) + (x << 3) + c - 48;
    c = getchar();
    }
    } int n, a[128][128], b[128][128];
    Graph<256, 32768> G; double check(double c)
    {
    G.clear();
    G.s = n + n + 2, G.n = G.t = G.s + 1;
    for(int i = 1; i <= n; ++i)
    for(int j = 1; j <= n; ++j)
    G.add_pipe(i, j + n, 1, - a[i][j] + c * b[i][j]);
    for(int i = 1; i <= n; ++i)
    {
    G.add_pipe(G.s, i, 1, 0);
    G.add_pipe(i + n, G.t, 1, 0);
    }
    return G.costflow().first;
    } int main()
    {
    read(n);
    for(int i = 1; i <= n; ++i)
    for(int j = 1; j <= n; ++j)
    read(a[i][j]);
    for(int i = 1; i <= n; ++i)
    for(int j = 1; j <= n; ++j)
    read(b[i][j]); double l = 0, r = 1e6, mid;
    while(r - l > eps)
    {
    mid = (l + r) / 2;
    if(check(mid) < 0) l = mid;
    else r = mid;
    } cout << fixed << setprecision(6) << mid << endl; return 0;
    }

最新文章

  1. Makefile编译
  2. 【腾讯bugly干货】QQ空间直播秒开优化实践
  3. java 接口(interface)
  4. Grid组件 列头居中
  5. ipmi使用
  6. Force IE to Open Link in New Tab
  7. 使用Fragment实现类似TabHost标签栏的效果
  8. statspack系列2
  9. 学习OpenSeadragon之四(导航视图)
  10. HLG 2163 方格取数 (最大网络流)
  11. Java 环境设置
  12. Nearest Common Ancestors (POJ 1330)
  13. GAN 教程记录
  14. git命令设置简写(别名)
  15. python3线程启动与停止
  16. sofa graphql 2 rest api webhook 试用
  17. python学习笔记_week12_mysql
  18. 怎样安装Scrapy
  19. 【BZOJ1441】Min 拓展裴蜀定理
  20. VScode设置jsx语法自动补全

热门文章

  1. Zookeeper启动流程分析
  2. stackoverflow的ret2syscall利用
  3. format的实现
  4. C#开发PACS医学影像处理系统(二):界面布局之菜单栏
  5. UNIX编程艺术
  6. JZOJ1496 页
  7. c,c++变量
  8. algorithm入门算法中的常见问题
  9. html+css入门基础案例之页面设计
  10. archaius(3) 配置管理器