题意:
R×C的网格中有N个点,选取其中的若干行和若干列将这些点全部覆盖(选取一行/列可以覆盖该行/列的所有点,要求每个点至少被覆盖一次)。
选取第i行的代价为r[i],选取第i列的代价为c[i],求代价乘积的最小值。
R, C, N≤50,r[i], c[i]均为≥1.0的正实数。
转化成最小割问题。建图方法如下:
(1)设立源点S和汇点T,对于每行、每列分别建立一个对应的节点;
(2)从源点出发,向每行连一条边,权值为r[i]的对数(底数自拟)。转化成对数可以将乘积最优问题转化为和最优。
(3)从每列出发,分别向汇点连一条边,权值为c[i]的对数;
(4)对于网格中的每一个点,设它在第x行第y列,在图中从第x行向第y列连一条边,权值为+∞。
注意:由于诡异的精度误差,inf的值取太大可能会导致WA!
代码如下:(前50行是个比较通用的大型边表模板,在赛场上如果有多道图论题的话可以省点时间……吧(这玩意儿会有卵用么))
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <queue> 5 #include <cmath> 6 7 #define FILL(a, c) memset(a, c, sizeof(a)) 8 9 template <int V, int E> //V为最大节点数,E为最大边数 10 struct EList 11 { 12 struct Edge { int to; double flow; }; 13 struct Node: Edge 14 { 15 int next; 16 void assign(int t, int n, double f) { this->to = t; next = n; this->flow = f; } 17 }; 18 struct Iter //迭代从节点v出发的所有边 19 { 20 EList *par; 21 int e; 22 Iter& operator ++ () { e = par->elist[e].next; return *this; } 23 bool operator != (const Iter& rhs) const { return e != rhs.e; } 24 //operator*返回的是引用类型 25 Edge& operator * () const { return par->elist[e]; } //只保留基类部分,不含next 26 Edge* operator -> () const { return par->elist + e; } 27 //(适用于网络流等问题)返回反向边 28 Edge& inv() const { return par->elist[e ^ 1]; } 29 }; 30 struct View //用于遍历从某个节点出发的所有边的视图,配合C++11的range-based for使用 31 { 32 EList *par; 33 int v; 34 Iter begin() const { return { par, par->head[v] }; } 35 Iter end() const { return { par, -1 }; } //用-1表示结束 36 }; 37 38 Node elist[E]; 39 int head[V]; 40 int ecnt; 41 42 void init() 43 { 44 FILL(head, -1); //用-1表示结束 45 ecnt = 0; 46 } 47 void add(int u, int v, double f) 48 { 49 elist[ecnt].assign(v, head[u], f); 50 head[u] = ecnt++; 51 elist[ecnt].assign(u, head[v], 0.0); 52 head[v] = ecnt++; 53 } 54 //返回从v节点出发的所有边的视图 55 View view(int v) { return { this, v }; } //如果编译错误,试着将this指针const_cast一下 56 //迭代从v出发的所有边的另一种方式,对于网络流等需要访问反向边的场合,View就不太适合了 57 Iter begin(int v) { return { this, head[v] }; } 58 Iter end(int v = -1) { return { this, -1 }; } 59 }; 60 61 int R, C, N, S, T; 62 EList<110, 123456> elist; 63 64 void input() 65 { 66 scanf("%d%d%d", &R, &C, &N); 67 elist.init(); 68 S = 0; 69 T = R + C + 1; 70 71 for (int i = 1; i <= R; i++) 72 { 73 double v; 74 scanf("%lf", &v); 75 elist.add(S, i, log2(v)); 76 } 77 for (int i = 1; i <= C; i++) 78 { 79 double v; 80 scanf("%lf", &v); 81 elist.add(i + R, T, log2(v)); 82 } 83 84 for (int u, v, i = 1; i <= N; i++) 85 { 86 scanf("%d%d", &u, &v); 87 elist.add(u, R + v, 1e50); 88 } 89 } 90 91 int layer[110]; 92 std::queue<int> que; 93 94 bool bfs() 95 { 96 FILL(layer, -1); 97 que.push(S); 98 layer[S] = 0; 99 100 while (!que.empty()) 101 { 102 int cur = que.front(); 103 que.pop(); 104 105 for (auto& e: elist.view(cur)) 106 { 107 if (layer[e.to] == -1 && e.flow > 0.0) 108 { 109 layer[e.to] = layer[cur] + 1; 110 que.push(e.to); 111 } 112 } 113 } 114 return layer[T] != -1; 115 } 116 117 //#include <fmt/format.h> 118 //using namespace fmt::literals; 119 120 double dfs(int cur, double rem) 121 { 122 if (cur == T) 123 return rem; 124 125 double origRem = rem; 126 for (auto it = elist.begin(cur); rem > 0.0 && it != elist.end(); ++it) 127 { 128 if (layer[it->to] == layer[cur] + 1 && it->flow > 0.0) 129 { 130 double n = dfs(it->to, std::min(rem, it->flow)); 131 //fmt::print("cur = {cur}, to = {to}, flow = {flow:.3f} ", "cur"_a = cur, "to"_a = it->to, "flow"_a = n); 132 rem -= n; 133 it->flow -= n; 134 it.inv().flow += n; 135 } 136 } 137 return origRem - rem; 138 } 139 140 double solve() 141 { 142 double ans = 0.0; 143 while (bfs()) 144 ans += dfs(S, 100); 145 return pow(2.0, ans); 146 } 147 148 int main() 149 { 150 int T; 151 for (scanf("%d", &T); T; T--) 152 { 153 input(); 154 printf("%.4f ", solve()); 155 } 156 return 0; 157 }