From 13dc0345599d0e0bc43e01bf2bf2785862d1b69d Mon Sep 17 00:00:00 2001 From: Jing Guan Date: Fri, 5 May 2017 19:45:03 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9C=80=E5=B0=8F=E8=B4=B9=E7=94=A8=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 49 +++++++++-------- src/aoj2230.cpp | 136 ++++++++++++++++++++++++++++++++++++++++++++++++ src/aoj2266.cpp | 109 ++++++++++++++++++++++++++++++++++++++ src/poj2195.cpp | 117 +++++++++++++++++++++++++++++++++++++++++ src/poj3068.cpp | 105 +++++++++++++++++++++++++++++++++++++ src/poj3422.cpp | 107 +++++++++++++++++++++++++++++++++++++ 6 files changed, 598 insertions(+), 25 deletions(-) create mode 100644 src/aoj2230.cpp create mode 100644 src/aoj2266.cpp create mode 100644 src/poj2195.cpp create mode 100644 src/poj3068.cpp create mode 100644 src/poj3422.cpp diff --git a/README.md b/README.md index 985dea8..1fb8612 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ # 1 前言 -项目为[《挑战程序设计竞赛(第2版)》](http://www.ituring.com.cn/book/1044)习题册,配合书籍或笔记,系统学习算法。 +项目为[《挑战程序设计竞赛(第2版)》](http://www.ituring.com.cn/book/1044)习题册攻略,已完结。可配合书籍或笔记,系统学习算法。 * 题量:约200道,代码注释内含详解。 * 难度:总体高于Leetcode,部分接近ACM。 * 题解:代码均AC,题解个人向;Bug或优化请建Issue或Pull Request。 -* 计划:持续更新,保证2017年5月初完成。 # 1.1 题库来源 - Google Code Jam([GCJ](https://code.google.com/codejam)) @@ -22,29 +21,29 @@ # 1.3 题库目录 * 初级: -[穷竭搜索 √](#21-穷竭搜索)、 -[贪心法 √](#22-贪心法)、 -[动态规划 √](#23-动态规划)、 -[数据结构 √](#24-数据结构)、 -[图论 √](#25-图论)、 -[数论 √](#26-数论) +[穷竭搜索](#21-穷竭搜索)、 +[贪心法](#22-贪心法)、 +[动态规划](#23-动态规划)、 +[数据结构](#24-数据结构)、 +[图论](#25-图论)、 +[数论](#26-数论) * 中级: -[二分搜索 √](#31-二分搜索)、 -[常用技巧 √](#32-常用技巧)、 -[数据结构(二) √](#33-数据结构二)、 -[动态规划(二) √](#34-动态规划二)、 +[二分搜索](#31-二分搜索)、 +[常用技巧](#32-常用技巧)、 +[数据结构(二)](#33-数据结构二)、 +[动态规划(二)](#34-动态规划二)、 [网络流](#35-网络流)、 -[计算几何 √](#36-计算几何) +[计算几何](#36-计算几何) * 高级: -[数论(二) √](#41-数论二)、 -[博弈论 √](#42-博弈论)、 -[图论(二) √](#43-图论二)、 -[常用技巧(二) √](#44-常用技巧二)、 -[智慧搜索 √](#45-智慧搜索)、 -[分治 √](#46-分治)、 -[字符串 √](#47-字符串) +[数论(二)](#41-数论二)、 +[博弈论](#42-博弈论)、 +[图论(二)](#43-图论二)、 +[常用技巧(二)](#44-常用技巧二)、 +[智慧搜索](#45-智慧搜索)、 +[分治](#46-分治)、 +[字符串](#47-字符串) # 2 初级算法 @@ -232,11 +231,11 @@ - [x] [AOJ 2251: Merry Christmas](http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=2251) - 最小费用流 - - [ ] [POJ 3068: ](http://poj.org/problem?id=3068) - - [ ] [POJ 2195: ](http://poj.org/problem?id=2195) - - [ ] [POJ 3422: ](http://poj.org/problem?id=3422) - - [ ] [AOJ 2266: ](http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=2266) - - [ ] [AOJ 2230: ](http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=2230) + - [x] [POJ 3068: "Shortest" pair of paths](http://poj.org/problem?id=3068) + - [x] [POJ 2195: Going Home](http://poj.org/problem?id=2195) + - [x] [POJ 3422: Kaka's Matrix Travels](http://poj.org/problem?id=3422) + - [x] [AOJ 2266: Cache Strategy](http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=2266) + - [x] [AOJ 2230: How to Create a Good Game](http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=2230) # 3.6 计算几何 - 极限 diff --git a/src/aoj2230.cpp b/src/aoj2230.cpp new file mode 100644 index 0000000..305e353 --- /dev/null +++ b/src/aoj2230.cpp @@ -0,0 +1,136 @@ +/* + * AOJ 2230: How to Create a Good Game + * 题意:给出一个带权有向无环图,在保证起点到终点的最长路不变前提下,可增加一些边的权。求最大能增加的边权总和。 + * 类型:最小费 + * 算法:输入的边两端连接容量INF花费为负边权的边,Dijkstra求最长路。从终点到起点连接容量无穷、花费为最长路的边。构造源s汇t,s与入度大于出度的点连容量度差花费的本容量1花费0的边,出度大的向t连类似的边。求得的最小费为所有圈的正权和,再加上负权和(输入边权和的相反数)即为答案。 + */ + +#include +#include +#include +#include + +using namespace std; + +typedef pair pii; + +const int INF = 0x3f3f3f3f; + +int in[110], out[110]; + +struct Edge { + Edge() {} + + Edge(int _v, int _cap, int _cost, int _rev) : v(_v), cap(_cap), cost(_cost), rev(_rev) {} + + int v, cap, cost, rev; +}; + +vector e[110]; +int n; +int h[110], d[110]; +int pv[110], pe[110]; +priority_queue, greater > pq; + +void AddEdge(int u, int v, int cap, int cost) { + e[u].push_back(Edge(v, cap, cost, e[v].size())); + e[v].push_back(Edge(u, 0, -cost, e[u].size() - 1)); +} + +int MinCostFlow(int s, int t, int f) { + int ans = 0; + memset(h, 0, sizeof(h)); + while (f > 0) { + memset(d, 0x3f, sizeof(d)); + d[s] = 0; + pq.push(make_pair(0, s)); + while (!pq.empty()) { + int u = pq.top().second; + int pre = pq.top().first; + pq.pop(); + if (d[u] < pre) continue; + for (int i = 0; i < e[u].size(); ++i) { + Edge &te = e[u][i]; + int v = te.v; + if (te.cap > 0 && d[v] > d[u] + h[u] - h[v] + te.cost) { + d[v] = d[u] + h[u] - h[v] + te.cost; + pv[v] = u; + pe[v] = i; + pq.push(make_pair(d[v], v)); + } + } + } + + if (d[t] == INF) { + return -1; + } + for (int i = 0; i < n; ++i) { + h[i] += d[i]; + } + + int cur = f; + for (int i = t; i != s; i = pv[i]) { + cur = min(cur, e[pv[i]][pe[i]].cap); + } + ans += cur * h[t]; + f -= cur; + for (int i = t; i != s; i = pv[i]) { + Edge &te = e[pv[i]][pe[i]]; + te.cap -= cur; + e[i][te.rev].cap += cur; + } + } + return ans; +} + +int main() { + int m; + scanf("%d%d", &n, &m); + int s = n, t = n + 1; + int sum = 0; + for (int i = 0; i < m; ++i) { + int u, v, c; + scanf("%d%d%d", &u, &v, &c); + AddEdge(u, v, INF, -c); + sum += c; + ++in[v]; + ++out[u]; + } + int degree = 0; + for (int i = 0; i < n; ++i) { + if (in[i] > out[i]) { + degree += in[i] - out[i]; + } + } + + memset(d, 0x3f, sizeof(d)); + d[0] = 0; + pq.push(make_pair(0, 0)); + while (!pq.empty()) { + int u = pq.top().second; + int pre = pq.top().first; + pq.pop(); + if (d[u] < pre) continue; + for (int i = 0; i < e[u].size(); ++i) { + Edge &te = e[u][i]; + int v = te.v; + if (te.cap > 0 && d[v] > d[u] + te.cost) { + d[v] = d[u] + te.cost; + pq.push(make_pair(d[v], v)); + } + } + } + + AddEdge(n - 1, 0, INF, -d[n - 1]); + for (int i = 0; i < n; ++i) { + if (in[i] > out[i]) { + AddEdge(s, i, in[i] - out[i], 0); + } else { + AddEdge(i, t, out[i] - in[i], 0); + } + } + n += 2; + int ans = MinCostFlow(s, t, degree) - sum; + printf("%d\n", ans); + return 0; +} diff --git a/src/aoj2266.cpp b/src/aoj2266.cpp new file mode 100644 index 0000000..780179e --- /dev/null +++ b/src/aoj2266.cpp @@ -0,0 +1,109 @@ +/* + * AOJ 2266: Cache Strategy + * 题意:有n个重量不同的球和m个框,再给出一个可重复的放球序列。依照序列顺序将此刻不在框内的球放入,若选择放入的框已有球,则需花费新球数量的重量来替换。求最小总花费。 + * 类型:最小费 + * 算法:将输入序列相邻相等的驱去重后,如每次都替换,则总花费为新序列的重量和。在序列中,两个相同的球之间的左闭右开序列区间如果放在同一框内,则总花费可减去该球重量。建图时可将上述区间的起点和终点建容量1花费负重量的边,再用容量为INF花费0的边将整个图连起来。求最小费用流即为可以节约的最大值的相反数,加上预处理的原始总重量即为答案。因为图有负环,所以求最短增广路不能用Dijkstra,改用Bellman-Ford。 + */ + +#include +#include +#include +#include + +using namespace std; + +typedef pair pii; + +const int INF = 0x3f3f3f3f; + +int a[10010], w[10010]; +int pre[10010]; + +struct Edge { + Edge() {} + + Edge(int _v, int _cap, int _cost, int _rev) : v(_v), cap(_cap), cost(_cost), rev(_rev) {} + + int v, cap, cost, rev; +}; + +vector e[10010]; +int n; +int d[10010]; +int pv[10010], pe[10010]; + +void AddEdge(int u, int v, int cap, int cost) { + e[u].push_back(Edge(v, cap, cost, e[v].size())); + e[v].push_back(Edge(u, 0, -cost, e[u].size() - 1)); +} + +int MinCostFlow(int s, int t, int f) { + int ans = 0; + while (f > 0) { + memset(d, 0x3f, sizeof(d)); + d[s] = 0; + bool update = true; + while (update) { + update = false; + for (int i = 0; i < n; ++i) { + if (d[i] == INF) continue; + for (int j = 0; j < e[i].size(); ++j) { + Edge &te = e[i][j]; + if (te.cap > 0 && d[te.v] > d[i] + te.cost) { + d[te.v] = d[i] + te.cost; + pv[te.v] = i; + pe[te.v] = j; + update = true; + } + } + } + } + + if (d[t] == INF) { + return -1; + } + + int cur = f; + for (int i = t; i != s; i = pv[i]) { + cur = min(cur, e[pv[i]][pe[i]].cap); + } + ans += cur * d[t]; + f -= cur; + for (int i = t; i != s; i = pv[i]) { + Edge &te = e[pv[i]][pe[i]]; + te.cap -= cur; + e[i][te.rev].cap += cur; + } + } + return ans; +} + +int main() { + int M, N, K, ans = 0; + scanf("%d%d%d", &M, &N, &K); + for (int i = 0; i < N; ++i) { + scanf("%d", &w[i]); + } + for (int i = 0; i < K; ++i) { + scanf("%d", &a[i]); + --a[i]; + } + K = unique(a, a + K) - a; + + memset(pre, -1, sizeof(pre)); + for (int i = 0; i < K; ++i) { + ans += w[a[i]]; + if (pre[a[i]] != -1) { + AddEdge(pre[a[i]], i - 1, 1, -w[a[i]]); + } + pre[a[i]] = i; + } + for (int i = 1; i < K; ++i) { + AddEdge(i - 1, i, M - 1, 0); + } + n = K; + ans += MinCostFlow(0, n - 1, M - 1); + printf("%d\n", ans); + + return 0; +} diff --git a/src/poj2195.cpp b/src/poj2195.cpp new file mode 100644 index 0000000..430ebe9 --- /dev/null +++ b/src/poj2195.cpp @@ -0,0 +1,117 @@ +/* + * POJ 2195: Going Home + * 题意:给出一个矩阵,图中包含n个人和n个房子。为每个人指派一间房子,花费是两者的哈密顿距离。求最小总花费。 + * 类型:最小费 + * 算法:构造源s汇t,人和房子间建立容量1花费为距离的边,s与人、房子与t间建立容量1花费为0的边。Dijsktra得到最短路,然后在最短路上寻找增广路。 + */ + +#include +#include +#include +#include +#include + +using namespace std; + +typedef pair pii; + +const int INF = 0x3f3f3f3f; + +char mat[110][110]; +pii man[110], house[110]; + +struct Edge { + Edge() {} + + Edge(int _v, int _cap, int _cost, int _rev) : v(_v), cap(_cap), cost(_cost), rev(_rev) {} + + int v, cap, cost, rev; +}; + +vector e[210]; +int n; +int h[210], d[210]; +int pv[210], pe[210]; +priority_queue, greater > pq; + +void AddEdge(int u, int v, int cap, int cost) { + e[u].push_back(Edge(v, cap, cost, e[v].size())); + e[v].push_back(Edge(u, 0, -cost, e[u].size() - 1)); +} + +int MinCostFlow(int s, int t, int f) { + int ans = 0; + memset(h, 0, sizeof(h)); + while (f > 0) { + memset(d, 0x3f, sizeof(d)); + d[s] = 0; + pq.push(make_pair(0, s)); + while (!pq.empty()) { + int u = pq.top().second; + int pre = pq.top().first; + pq.pop(); + if (d[u] < pre) continue; + for (int i = 0; i < e[u].size(); ++i) { + Edge &te = e[u][i]; + int v = te.v; + if (te.cap > 0 && d[v] > d[u] + h[u] - h[v] + te.cost) { + d[v] = d[u] + h[u] - h[v] + te.cost; + pv[v] = u; + pe[v] = i; + pq.push(make_pair(d[v], v)); + } + } + } + + if (d[t] == INF) { + return -1; + } + for (int i = 0; i < n; ++i) { + h[i] += d[i]; + } + + int cur = f; + for (int i = t; i != s; i = pv[i]) { + cur = min(cur, e[pv[i]][pe[i]].cap); + } + ans += cur * h[t]; + f -= cur; + for (int i = t; i != s; i = pv[i]) { + Edge &te = e[pv[i]][pe[i]]; + te.cap -= cur; + e[i][te.rev].cap += cur; + } + } + return ans; +} + +int main() { + int r, c; + while (scanf("%d%d", &r, &c) != EOF && r) { + int nm = 0, nh = 0; + for (int i = 0; i < r; ++i) { + scanf("%s", mat[i]); + for (int j = 0; j < c; ++j) { + if (mat[i][j] == 'm') { + man[nm++] = make_pair(i, j); + } else if (mat[i][j] == 'H') { + house[nh++] = make_pair(i, j); + } + } + } + n = nm * 2 + 2; + int s = n - 2, t = n - 1; + for (int i = 0; i < n; ++i) e[i].clear(); + for (int i = 0; i < nm; ++i) { + AddEdge(s, i, 1, 0); + AddEdge(i + nm, t, 1, 0); + } + for (int i = 0; i < nm; ++i) { + for (int j = 0; j < nm; ++j) { + AddEdge(i, nm + j, 1, abs(man[i].first - house[j].first) + abs(man[i].second - house[j].second)); + } + } + printf("%d\n", MinCostFlow(s, t, nm)); + } + return 0; +} \ No newline at end of file diff --git a/src/poj3068.cpp b/src/poj3068.cpp new file mode 100644 index 0000000..32a3995 --- /dev/null +++ b/src/poj3068.cpp @@ -0,0 +1,105 @@ +/* + * POJ 3068: "Shortest" pair of paths + * 题意:给出一个带权有向图,求两条从起点到终点中间无公共点的路的边权和最小值。 + * 类型:最小费 + * 算法:每个点拆为2个,起点和终点与其副本间连接容量2花费0的边,其余每个点和它的副本之间连接容量1花费0的边,输入的边连接开端和到达的副本容量1花费为边权。求解流为2的最小费用流即为答案。 + */ + +#include +#include +#include +#include + +using namespace std; + +typedef pair pii; + +const int INF = 0x3f3f3f3f; + +struct Edge { + Edge() {} + + Edge(int _v, int _cap, int _cost, int _rev) : v(_v), cap(_cap), cost(_cost), rev(_rev) {} + + int v, cap, cost, rev; +}; + +vector e[130]; +int n; +int h[130], d[130]; +int pv[130], pe[130]; +priority_queue, greater > pq; + +void AddEdge(int u, int v, int cap, int cost) { + e[u].push_back(Edge(v, cap, cost, e[v].size())); + e[v].push_back(Edge(u, 0, -cost, e[u].size() - 1)); +} + +int MinCostFlow(int s, int t, int f) { + int ans = 0; + memset(h, 0, sizeof(h)); + while (f > 0) { + memset(d, 0x3f, sizeof(d)); + d[s] = 0; + pq.push(make_pair(0, s)); + while (!pq.empty()) { + int u = pq.top().second; + int pre = pq.top().first; + pq.pop(); + if (d[u] < pre) continue; + for (int i = 0; i < e[u].size(); ++i) { + Edge &te = e[u][i]; + int v = te.v; + if (te.cap > 0 && d[v] > d[u] + h[u] - h[v] + te.cost) { + d[v] = d[u] + h[u] - h[v] + te.cost; + pv[v] = u; + pe[v] = i; + pq.push(make_pair(d[v], v)); + } + } + } + + if (d[t] == INF) { + return -1; + } + for (int i = 0; i < n; ++i) { + h[i] += d[i]; + } + + int cur = f; + for (int i = t; i != s; i = pv[i]) { + cur = min(cur, e[pv[i]][pe[i]].cap); + } + ans += cur * h[t]; + f -= cur; + for (int i = t; i != s; i = pv[i]) { + Edge &te = e[pv[i]][pe[i]]; + te.cap -= cur; + e[i][te.rev].cap += cur; + } + } + return ans; +} + +int main() { + int m, tc = 0; + while (scanf("%d%d", &n, &m) != EOF && n) { + for (int i = 0; i < n << 1; ++i) e[i].clear(); + for (int i = 0; i < m; ++i) { + int u, v, c; + scanf("%d%d%d", &u, &v, &c); + AddEdge(u + n, v, 1, c); + } + AddEdge(0, n, 2, 0); + AddEdge(n - 1, n - 1 + n, 2, 0); + for (int i = 1; i < n - 1; ++i) { + AddEdge(i, i + n, 1, 0); + } + n <<= 1; + int ans = MinCostFlow(0, n - 1, 2); + printf("Instance #%d: ", ++tc); + if (ans == -1) printf("Not possible\n"); + else printf("%d\n", ans); + } + return 0; +} diff --git a/src/poj3422.cpp b/src/poj3422.cpp new file mode 100644 index 0000000..af87410 --- /dev/null +++ b/src/poj3422.cpp @@ -0,0 +1,107 @@ +/* + * POJ 3422: Kaka's Matrix Travels + * 题意:给出一个矩阵,每个点的价值非负。从左上走到右下,每路过一个点将其价值累加入答案,并将该点置0。求走k次能收获的最大总价值。 + * 类型:最小费 + * 算法:把每个点拆为2个,之间连接容量k花费负价值的边。构造源s汇t,s与原节点、原节点的副本连容量k花费0的边。最小费用流求得的相反数即为答案。 + */ + +#include +#include +#include +#include + +using namespace std; + +typedef pair pii; + +const int INF = 0x3f3f3f3f; + +struct Edge { + Edge() {} + + Edge(int _v, int _cap, int _cost, int _rev) : v(_v), cap(_cap), cost(_cost), rev(_rev) {} + + int v, cap, cost, rev; +}; + +vector e[5010]; +int n; +int h[5010], d[5010]; +int pv[5010], pe[5010]; +priority_queue, greater > pq; + +void AddEdge(int u, int v, int cap, int cost) { + e[u].push_back(Edge(v, cap, cost, e[v].size())); + e[v].push_back(Edge(u, 0, -cost, e[u].size() - 1)); +} + +int MinCostFlow(int s, int t, int f) { + int ans = 0; + memset(h, 0, sizeof(h)); + while (f > 0) { + memset(d, 0x3f, sizeof(d)); + d[s] = 0; + pq.push(make_pair(0, s)); + while (!pq.empty()) { + int u = pq.top().second; + int pre = pq.top().first; + pq.pop(); + if (d[u] < pre) continue; + for (int i = 0; i < e[u].size(); ++i) { + Edge &te = e[u][i]; + int v = te.v; + if (te.cap > 0 && d[v] > d[u] + h[u] - h[v] + te.cost) { + d[v] = d[u] + h[u] - h[v] + te.cost; + pv[v] = u; + pe[v] = i; + pq.push(make_pair(d[v], v)); + } + } + } + + if (d[t] == INF) { + return -1; + } + for (int i = 0; i < n; ++i) { + h[i] += d[i]; + } + + int cur = f; + for (int i = t; i != s; i = pv[i]) { + cur = min(cur, e[pv[i]][pe[i]].cap); + } + ans += cur * h[t]; + f -= cur; + for (int i = t; i != s; i = pv[i]) { + Edge &te = e[pv[i]][pe[i]]; + te.cap -= cur; + e[i][te.rev].cap += cur; + } + } + return ans; +} + +int main() { + int r, k; + scanf("%d%d", &r, &k); + n = r * r; + int s, t; + s = n * 2; + t = s + 1; + for (int i = 0; i < n; ++i) { + int c; + scanf("%d", &c); + AddEdge(i, i + n, 1, -c); + AddEdge(s, i, k, 0); + AddEdge(i + n, t, k, 0); + if (i % r) { + AddEdge(i - 1 + n, i, k, 0); + } + if (i < n - r) { + AddEdge(i + n, i + r, k, 0); + } + } + n = t + 1; + printf("%d\n", -MinCostFlow(s, t, k)); + return 0; +}