题解: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)\) 函数能实现对应功能的话,就只需要这样写:

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

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

接下来考虑如何实现这个 \(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的版本。

    代码:

    1. #include<bits/stdc++.h>
    2. using namespace std;
    3. const double eps = 1e-7;
    4. template<const int N, const int M>
    5. class Graph {
    6. private:
    7. typedef pair<double, int> Node;
    8. priority_queue<Node> que;
    9. const double INF = 1e7;
    10. int beg[N], nex[M], tar[M], cap[M], ite[N], len;
    11. double pot[N], dis[N], cst[M];
    12. bool vis[N];
    13. public:
    14. int n, s, t;
    15. inline void clear() {
    16. memset(pot, 0, sizeof(pot));
    17. memset(beg, 0, sizeof(beg));
    18. memset(nex, 0, sizeof(nex));
    19. len = 1;
    20. }
    21. Graph() {
    22. clear();
    23. }
    24. inline void add_edge(int a, int b, int c, double d)
    25. {
    26. ++len, tar[len] = b, cap[len] = c, cst[len] = d;
    27. nex[len] = beg[a], beg[a] = len;
    28. }
    29. inline void add_pipe(int a, int b, int c, double d)
    30. {
    31. add_edge(a, b, c, +d);
    32. add_edge(b, a, 0, -d);
    33. }
    34. inline bool dijkstra(int s, int t)
    35. {
    36. fill(dis, dis + n + 1, INF);
    37. que.push(Node(dis[s] = 0, s));
    38. while(!que.empty())
    39. {
    40. Node cur = que.top(); que.pop();
    41. int u = cur.second;
    42. if(-cur.first > dis[u]) continue;
    43. for(int i = beg[u]; i; i = nex[i])
    44. {
    45. int v = tar[i];
    46. double tmp = dis[u] + cst[i] + pot[u] - pot[v];
    47. if(cap[i] && dis[v]- tmp > eps)
    48. que.push(Node(-(dis[v] = tmp), v));
    49. }
    50. }
    51. return dis[t] < INF;
    52. }
    53. int dfs(int u, int flo)
    54. {
    55. if(u == t) return flo;
    56. int rst = flo;
    57. vis[u] = true;
    58. for(int &i = ite[u]; i; i = nex[i])
    59. {
    60. int v = tar[i];
    61. if(vis[v] || !cap[i]) continue;
    62. double tmp = dis[u] + cst[i] + pot[u] - pot[v];
    63. if(fabs(tmp - dis[v]) < eps)
    64. {
    65. int res = dfs(v, min(rst, cap[i]));
    66. rst -= res;
    67. cap[i] -= res;
    68. cap[i ^ 1] += res;
    69. }
    70. }
    71. vis[u] = false;
    72. return flo - rst;
    73. }
    74. inline Node costflow()
    75. {
    76. Node ret = Node(0, 0);
    77. while(dijkstra(s, t))
    78. {
    79. memcpy(ite, beg, sizeof(ite));
    80. int res = dfs(s, INF);
    81. for(int i = 1; i <= n; ++i)
    82. if(dis[i] < INF) pot[i] += dis[i];
    83. ret.first += res * pot[t];
    84. ret.second += res;
    85. }
    86. return ret;
    87. }
    88. };
    89. template<class T>
    90. inline void read(T &x)
    91. {
    92. char c = getchar(); x = 0;
    93. while(c < '0' || '9' < c) c = getchar();
    94. while('0' <= c && c <= '9')
    95. {
    96. x = (x << 1) + (x << 3) + c - 48;
    97. c = getchar();
    98. }
    99. }
    100. int n, a[128][128], b[128][128];
    101. Graph<256, 32768> G;
    102. double check(double c)
    103. {
    104. G.clear();
    105. G.s = n + n + 2, G.n = G.t = G.s + 1;
    106. for(int i = 1; i <= n; ++i)
    107. for(int j = 1; j <= n; ++j)
    108. G.add_pipe(i, j + n, 1, - a[i][j] + c * b[i][j]);
    109. for(int i = 1; i <= n; ++i)
    110. {
    111. G.add_pipe(G.s, i, 1, 0);
    112. G.add_pipe(i + n, G.t, 1, 0);
    113. }
    114. return G.costflow().first;
    115. }
    116. int main()
    117. {
    118. read(n);
    119. for(int i = 1; i <= n; ++i)
    120. for(int j = 1; j <= n; ++j)
    121. read(a[i][j]);
    122. for(int i = 1; i <= n; ++i)
    123. for(int j = 1; j <= n; ++j)
    124. read(b[i][j]);
    125. double l = 0, r = 1e6, mid;
    126. while(r - l > eps)
    127. {
    128. mid = (l + r) / 2;
    129. if(check(mid) < 0) l = mid;
    130. else r = mid;
    131. }
    132. cout << fixed << setprecision(6) << mid << endl;
    133. return 0;
    134. }

题解:SDOI2017 新生舞会的更多相关文章

  1. 【BZOJ4819】[Sdoi2017]新生舞会 01分数规划+费用流

    [BZOJ4819][Sdoi2017]新生舞会 Description 学校组织了一次新生舞会,Cathy作为经验丰富的老学姐,负责为同学们安排舞伴.有n个男生和n个女生参加舞会 买一个男生和一个女 ...

  2. [Sdoi2017]新生舞会 [01分数规划 二分图最大权匹配]

    [Sdoi2017]新生舞会 题意:沙茶01分数规划 貌似\(*10^7\)变成整数更科学 #include <iostream> #include <cstdio> #inc ...

  3. BZOJ_4819_[Sdoi2017]新生舞会_01分数规划+费用流

    BZOJ_4819_[Sdoi2017]新生舞会_01分数规划+费用流 Description 学校组织了一次新生舞会,Cathy作为经验丰富的老学姐,负责为同学们安排舞伴.有n个男生和n个女生参加舞 ...

  4. 洛谷 P3705 [SDOI2017]新生舞会 解题报告

    P3705 [SDOI2017]新生舞会 题目描述 学校组织了一次新生舞会,\(Cathy\)作为经验丰富的老学姐,负责为同学们安排舞伴. 有\(n\)个男生和\(n\)个女生参加舞会买一个男生和一个 ...

  5. 【BZOJ 4819】 4819: [Sdoi2017]新生舞会 (0-1分数规划、二分+KM)

    4819: [Sdoi2017]新生舞会 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 601  Solved: 313 Description 学校 ...

  6. [BZOJ4819][SDOI2017]新生舞会(分数规划+费用流,KM)

    4819: [Sdoi2017]新生舞会 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1097  Solved: 566[Submit][Statu ...

  7. 【算法】01分数规划 --- HNOI2009最小圈 & APIO2017商旅 & SDOI2017新生舞会

    01分数规划:通常的问法是:在一张有 \(n\) 个点,\(m\) 条边的有向图中,每一条边均有其价值 \(v\) 与其代价 \(w\):求在图中的一个环使得这个环上所有的路径的权值和与代价和的比率最 ...

  8. 4819: [Sdoi2017]新生舞会(分数规划)

    4819: [Sdoi2017]新生舞会 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1031  Solved: 530[Submit][Statu ...

  9. P3705 [SDOI2017]新生舞会 01分数规划+费用流

    $ \color{#0066ff}{ 题目描述 }$ 学校组织了一次新生舞会,Cathy作为经验丰富的老学姐,负责为同学们安排舞伴. 有\(n\)个男生和\(n\)个女生参加舞会买一个男生和一个女生一 ...

随机推荐

  1. leetcode刷题-43字符串相乘

    题目 给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式. 思路 字符串转数字:从字符串第一位开始取,每次取出的值转换为数字 ...

  2. poi自动生成Ecxel表格和Chart图表

    最近因为业务需求,需要做poi自动导出Ecxel表格和Chart折线图的功能. 所以我在网上找到了一篇关于poi生成Chart图表的博客,代码很详细,但是缺少相关注释说明. 想要将它改造成自己需要的样 ...

  3. 微信小程序-组件-视图容器

    1.view 1.作用:类似 html 的 div 用来进行页面布局,具有块级盒子特性. 2.常用属性:设置view盒子点击后的状态,以及控制是否影响父盒子的点击状态 3.eg:<view ho ...

  4. python 3 continue 循环控制

  5. python里面的project、package、module分别是什么

    2020/5/25 1.project(项目) project 即项目,是一个很大的文件夹,里面有好多的 .py 文件. 在Spyder 中点击菜单栏 projects ----->  new ...

  6. 解决spark streaming集成kafka时只能读topic的其中一个分区数据的问题

    1. 问题描述 我创建了一个名称为myTest的topic,该topic有三个分区,在我的应用中spark streaming以direct方式连接kakfa,但是发现只能消费一个分区的数据,多次更换 ...

  7. 抢先学鸿蒙(HarmonyOS)2.0,你就是下一个大咖!

        1. 你不知道的鸿蒙(HarmonyOS)   2020年9月10日,华为开发者大会发布了鸿蒙(HarmonyOS)2.0.我在2020-9-11日也发布了全球首套鸿蒙2.0 App开发视频课 ...

  8. python爬取新浪财经

    我们来获取这里的title和url然后再获取这里面url的编辑作者 可以看到右边的几个就对应的左边不同的div .m-p1-mb2-list.m-list-container ul li a impo ...

  9. C++实现将一个文件夹内容拷贝至另一个文件夹

    Windows提供了非常好用的方法SHFileOperation,而且功能强大, 不光可以拷贝,还有移动.删除等等操作.直接上代码: 1 void CopyFolder(TCHAR* srcFolde ...

  10. Python3 环境搭建 保姆式 详细教程!真手把手教学!

    本文我们将向大家介绍如何在本地搭建 Python3 开发环境. Python3 可应用于多平台包括 Windows.Linux 和 Mac OS X. Unix (Solaris, Linux, Fr ...