1.P4015 运输问题
题目描述
W 公司有 m 个仓库和 n 个零售商店。第 i个仓库有 ai 个单位的货物;第 j 个零售商店需要 bj 个单位的货物。
货物供需平衡,即sum(a[1]->a[m]) == sum(a[1]->b[n])。
从第i个仓库运送每单位货物到第j个零售商店的费用为 cij。
试设计一个将仓库中所有货物运送到零售商店的运输方案,使总运输费用最少。
输入格式
第 1 行有 2个正整数 m 和 n,分别表示仓库数和零售商店数。
接下来的一行中有 m个正整数 ai,表示第 i个仓库有 ai个单位的货物。
再接下来的一行中有 n 个正整数 bj,表示第j个零售商店需要bj个单位的货物。
接下来的m行,每行有n个整数,表示从第i个仓库运送每单位货物到第j个零售商店的费用cij。
输出格式
两行分别输出最小运输费用和最大运输费用。
输入输出样例
#include <bits/stdc++.h> using namespace std; typedef unsigned long long ull; #define ll long long #define int long long const int maxn = 2e4 + 20; const int inf = 0x3f3f3f3f; const ll INF = 1ll << 62; const double eps = 1e-7; const int mod = 1e6 + 3; #define mem(a, b) memset(a, b, sizeof(a)) inline ll gcd(ll a, ll b) { return b == 0 ? a : gcd(b, a % b); } inline int rd() { int x; scanf("%lld", &x); return x; } int n, m, s, t; int head[maxn], cnt, b[maxn], _cost[110][220], a[maxn]; int dis[maxn] = {0}, fa[maxn], pre[maxn], inq[maxn]; struct Edge{ int v, w, c, nxt; }e[maxn]; void init(){ for(int i = 0; i <= n + m + 1; i++) head[i] = -1; cnt = 0; } void addEdge(int u, int v, int w, int c){ e[cnt].v = v; e[cnt].w = w; e[cnt].c = c; e[cnt].nxt = head[u]; head[u] = cnt++; } bool SPFA(){ for(int i = 0; i <= n + m + 1; i++){ dis[i] = inf; fa[i] = -1; pre[i] = -1; inq[i] = 0; } queue<int>q; while(!q.empty())q.pop(); q.push(s); inq[s] = 1; dis[s] = 0; pre[t] = -2; while(!q.empty()){ int u = q.front(); q.pop(); inq[u] = 0; for(int i = head[u]; ~i; i = e[i].nxt){ int v = e[i].v, w = e[i].w, c = e[i].c; if(dis[v] > dis[u] + c && w > 0){ dis[v] = dis[u] + c; pre[v] = i; fa[v] = u; if(!inq[v]){ q.push(v); inq[v] = 1; } } } } return (pre[t] != -2); } int MCMF(){ int min_cost = 0; while(SPFA()){ int min_flow = inf; int p = t; while(p != s){ min_flow = min(min_flow, e[pre[p]].w); p = fa[p]; } min_cost += min_flow * dis[t]; p = t; while(p != s){ e[pre[p]].w -= min_flow; e[pre[p] ^ 1].w += min_flow; p = fa[p]; } } return min_cost; } signed main(){ m = rd(); n = rd(); init(); s = 0; t = n + m + 1; for(int i = 1; i <= m; i++){ a[i] = rd(); } for(int i = m + 1; i <= n + m; i++){ b[i] = rd(); } for(int i = 1; i <= m; i++){ for(int j = m + 1; j <= n + m; j++){ _cost[i][j] = rd(); } } for(int i = 1; i <= m; i++){ addEdge(s, i, a[i], 0); addEdge(i, s, 0, 0); } for(int i = 1; i <= m; i++){ for(int j = m + 1; j <= n + m; j++){ addEdge(i, j, b[j], _cost[i][j]); addEdge(j, i, 0, -_cost[i][j]); } } for(int i = m + 1; i <= n + m; i++){ addEdge(i, t, b[i], 0); addEdge(t, i, 0, 0); } printf("%lld\n", MCMF()); init(); for(int i = 1; i <= m; i++){ addEdge(s, i, a[i], 0); addEdge(i, s, 0, 0); } for(int i = 1; i <= m; i++){ for(int j = m + 1; j <= n + m; j++){ addEdge(i, j, b[j], -_cost[i][j]); addEdge(j, i, 0, _cost[i][j]); } } for(int i = m + 1; i <= n + m; i++){ addEdge(i, t, b[i], 0); addEdge(t, i, 0, 0); } printf("%lld\n", -1*MCMF()); return 0; }
2.P4016 负载平衡问题
题目描述
g 公司有 n 个沿铁路运输线环形排列的仓库,每个仓库存储的货物数量不等。如何用最少搬运量可以使 g 个仓库的库存数量相同。搬运货物时,只能在相邻的仓库之间搬运。
输入格式
第一行一个正整数 n,表示有 n个仓库。(1 <= n <= 100)
第二行 n 个正整数,表示 n 个仓库的库存量。
输出格式
输出最少搬运量。
输入输出样例
输入:
5
17 9 14 16 4
输出:
11
思路:题意如上,该题可以用贪心更快地解决,这里采用网络流的方式解决该问题。首先老规矩先定义一个源点0和一个汇点n+1。该问题的目的是让每个仓库的库存数量相等,那么我们根据所给的数据可以算出平均值,我们可以这么理解,为了使得所有仓库的库存量等于平均值avg那么库存量大于avg的点需要流出自己的部分库存,小于avg的仓库需要有部分库存流入。根据这个特点我们可以构建图,从源点连出边到大于等于avg的仓库点容量为a[i]费用为0;从小于avg的仓库点连边到汇点容量为avg-a[i]费用为0;从大于等于avg的仓库点连边到小于avg的仓库点容量为a[i]-avg费用为需要调整的量其实就是取min(abs(u, v), n - (abs(u, v)))就是两个点在环上的距离(有两段);
#include <bits/stdc++.h> using namespace std; typedef unsigned long long ull; #define ll long long #define int long long const int maxn = 2e4 + 20; const int inf = 0x3f3f3f3f; const ll INF = 1ll << 62; const double eps = 1e-7; const int mod = 1e6 + 3; #define mem(a, b) memset(a, b, sizeof(a)) inline ll gcd(ll a, ll b) { return b == 0 ? a : gcd(b, a % b); } inline int rd() { int x; scanf("%lld", &x); return x; } int s, t; int cnt, head[maxn]; int n, a[maxn]; int fa[maxn], pre[maxn], inq[maxn], dis[maxn]; struct Edge{ int u, v, w, c, nxt; }e[maxn]; void init(){ cnt = 0; for(int i = 0; i <= n + 1; i++) head[i] = -1; } void addEdge(int u, int v, int w, int c){ e[cnt].v = v; e[cnt].w = w; e[cnt].c = c; e[cnt].nxt = head[u]; head[u] = cnt++; } bool SPFA(){ for(int i = 0; i <= n + 1; i++){ inq[i] = 0; pre[i] = -1; fa[i] = -1; dis[i] = inf; } queue<int>q; while(!q.empty())q.pop(); q.push(s); inq[s] = 1; dis[s] = 0; pre[t] = -2; while (!q.empty()){ int u = q.front(); q.pop(); inq[u] = 0; for(int i = head[u]; ~i; i = e[i].nxt){ int v = e[i].v, w = e[i].w, c = e[i].c; if(dis[v] > dis[u] + c && w > 0){ dis[v] = dis[u] + c; pre[v] = i; fa[v] = u; if(!inq[v]){ q.push(v); inq[v] = 1; } } } } return (pre[t] != -2); } int MCMF(){ int min_cost = 0; while (SPFA()){ int min_flow = inf; int p = t; while(p != s){ min_flow = min(min_flow, e[pre[p]].w); p = fa[p]; } min_cost += min_flow * dis[t]; p = t; while(p != s){ e[pre[p]].w -= min_flow; e[pre[p] ^ 1].w += min_flow; p = fa[p]; } } return min_cost; } signed main(){ n = rd(); int sum = 0, avg; for(int i = 1; i <= n; i++) a[i] = rd(), sum += a[i]; init(); avg = sum / n; s = 0, t = n + 1; vector<int>out, in; for(int i = 1; i <= n; i++){ if(a[i] >= avg){ addEdge(s, i, a[i] - avg, 0); addEdge(i, s, 0, 0); out.push_back(i); }else{ addEdge(i, t, avg - a[i] , 0); addEdge(t, i, 0, 0); in.push_back(i); } } for(auto i : out){ for(auto j : in){ int cost = abs(i - j); cost = min(cost, n - cost); addEdge(i, j, a[i] - avg, cost); addEdge(j, i, 0, -cost); } } printf("%lld\n", MCMF()); return 0; }
题目描述
问题描述:
假设一个试题库中有 n 道试题。每道试题都标明了所属类别。同一道题可能有多个类别属性。现要从题库中抽取 m 道题组成试卷。并要求试卷包含指定类型的试题。试设计一个满足要求的组卷算法。
编程任务:
对于给定的组卷要求,计算满足要求的组卷方案。
输入格式
第一行有两个正整数 k 和 n。k 表示题库中试题类型总数,n 表示题库中试题总数。
第二行有 k 个正整数,第 i 个正整数表示要选出的类型 i 的题数。这k 个数相加就是要选出的总题数 m。2<=k<=20,k<=n<=1e3
接下来的 n 行给出了题库中每个试题的类型信息。每行的第一个正整数 p 表明该题可以属于 p 类,接着的 p 个数是该题所属的类型号。
输出格式
输出共 k 行,第 i 行输出 i: 后接类型 i 的题号。
如果有多个满足要求的方案,只要输出一个方案。
如果问题无解,则输出No Solution!。
输入:
3 15
3 3 4
2 1 2
1 3
1 3
1 3
1 3
3 1 2 3
2 2 3
2 1 3
1 2
1 2
2 1 2
2 1 3
2 1 2
1 1
3 1 2 3
输出:
1: 14 6 1
2: 13 11 10
3: 15 12 8 7
思路:题意如上,看到这个问题其实并不会联想到网络流,但是这是网络流24题,我就不得不从网络流的角度来思考问题,经过抽象还是可以得到符合网络流模型(万物皆可网络流)。因为我们要获取每种类型需要的题目,虽然一个题目可以有多种类型,但是在题目中选择题目时,每个题目只能作为一种类型而被选择,就是每个题目只能被选择一次。那么我们就可以想到从题号连边到类型。网络流图的结构就可以得出:创建源点s = 0和汇点t = n+k+1,然后连接s和每一个题号容量为1,接着创建题号到类型的边容量为1,最后创建类型到汇点的边容量为该类型需要的题目数量。最后跑一遍最大流就好了。获取答案就是遍历类型的点到题号的点,如果这条边的容量变为1,则说明该边对应的点就是答案。(其实这条边就是题号点到类型点的反边,当反边容量从0变成1时说明这道题选择了这个类型,也就是流从题号点流到了类型点)。
#include <bits/stdc++.h> using namespace std; typedef unsigned long long ull; #define ll long long #define int long long const int maxn = 2e4 + 20; const int inf = 0x3f3f3f3f; const ll INF = 1ll << 62; const double eps = 1e-7; const int mod = 1e6 + 3; #define mem(a, b) memset(a, b, sizeof(a)) inline ll gcd(ll a, ll b) { return b == 0 ? a : gcd(b, a % b); } inline int rd() { int x = 0,f = 1;char ch = getchar(); while (!isdigit(ch)){if (ch == \'-\') f = -1;ch = getchar();} while (isdigit(ch)){x = x * 10 + ch - \'0\';ch = getchar();} return x * f; } void Put(ll x) { if (x < 0) putchar(\'-\'), x *= -1; if (x > 9) Put(x / 10); putchar(x % 10 + \'0\'); } struct Edge{ int u, v, w, nxt; }e[maxn]; int head[maxn], cnt, s, t, path[maxn]; void init(){ cnt = 0; for(int i = s; i <= t; i++) head[i] = -1; } void addEdge(int u, int v, int w){ e[cnt].v = v; e[cnt].w = w; e[cnt].nxt = head[u]; head[u] = cnt++; } int ned[maxn], k, n, deep[maxn]; bool bfs(){ mem(deep, 0); queue<int>q; while(!q.empty())q.pop(); q.push(s); deep[s] = 1; while(!q.empty()){ int u = q.front(); q.pop(); for(int i = head[u]; ~i; i = e[i].nxt){ int v = e[i].v, w = e[i].w; if(deep[v] == 0 && w > 0){ deep[v] = deep[u] + 1; q.push(v); } } } return (deep[t] != 0); } int dinic(int u, int flow){ if(u == t)return flow; int ans = 0; for(int i = head[u]; ~i; i = e[i].nxt){ int v = e[i].v, w = e[i].w; if(w > 0 && deep[v] == deep[u] + 1){ int tmp = dinic(v, min(flow, w)); ans += tmp; flow -= tmp; e[i].w -= tmp; e[i^1].w += tmp; } } return ans; } int maxFlow(){ int ans = 0; while(bfs()){ ans += dinic(s, inf); } return ans; } signed main(){ k = rd(), n = rd(); s = 0, t = n + k + 1; init(); for(int i = n + 1; i <= n + k; i++){ ned[i] = rd(); addEdge(i, t, ned[i]); addEdge(t, i, 0); } for(int i = 1; i <= n; i++){ int t = rd(); while(t--){ int x = rd(); x += n; addEdge(i, x, 1); addEdge(x, i, 0); } } for(int i = 1; i <= n; i++){ addEdge(s, i, 1); addEdge(i, s, 0); } // cout << "maxFlow: " << maxFlow() << endl; maxFlow(); int flag = 1; for(int i = n + 1; i <= n + k; i++){ for(int j = head[i]; ~j; j = e[j].nxt){ int v = e[j].v, w = e[j].w; if(v != t)continue; if(w != 0){ flag = 0; break; } } } if(!flag)printf("No Solution!\n"); else{ for(int i = n + 1; i <= n + k; i++){ printf("%lld:", i - n); for(int j = head[i]; ~j; j = e[j].nxt){ int v = e[j].v, w = e[j].w; if(v >= 1 && v <= n){ if(w == 1) printf(" %lld", v); } } printf("\n"); } } return 0; }
题目描述
假设有 n 根柱子,现要按下述规则在这 n 根柱子中依次放入编号为 1,2,3,...的球“
-
每次只能在某根柱子的最上面放球。
-
同一根柱子中,任何 2 个相邻球的编号之和为完全平方数。
试设计一个算法,计算出在 n 根柱子上最多能放多少个球。例如,在 4 根柱子上最多可放 11 个球。
对于给定的 n,计算在 n 根柱子上最多能放多少个球。
输入格式
只有一行一个整数 n,代表柱子数。(1 <= n <= 55)
输出格式
本题存在 Special Judge。
请将 n 根柱子上最多能放的球数以及相应的放置方案输出。
输出的第一行是球数。
接下来的 n 行,每行若干个整数,代表一根柱子上的球的编号,数字间用单个空格隔开。
输入输出样例
输入:
4
输出:
11
1 8
2 7 9
3 6 10
4 5 11
思路:题意如上。直接暴力梭哈可以过,网络流做法就是拆点成两个点,然后在两个点之间放入一个可以使得这个数和新加入的数的和为完全平方数。网络流做法做这题效率相当差,编写也麻烦,但是这种类似的思路可以学习借鉴。放上我的暴力美学代码。
#include <bits/stdc++.h> using namespace std; typedef unsigned long long ull; #define ll long long #define int long long const int maxn = 2e4 + 20; const int inf = 0x3f3f3f3f; const ll INF = 1ll << 62; const double eps = 1e-7; const int mod = 1e6 + 3; #define mem(a, b) memset(a, b, sizeof(a)) inline ll gcd(ll a, ll b) { return b == 0 ? a : gcd(b, a % b); } inline int rd() { int x = 0,f = 1;char ch = getchar(); while (!isdigit(ch)){if (ch == \'-\') f = -1;ch = getchar();} while (isdigit(ch)){x = x * 10 + ch - \'0\';ch = getchar();} return x * f; } void Put(ll x) { if (x < 0) putchar(\'-\'), x *= -1; if (x > 9) Put(x / 10); putchar(x % 10 + \'0\'); } bool check(int x, int y){ int z = sqrt(x + y); if(z * z == x + y)return true; return false; } signed main(){ int n = rd(); vector<int>v[60]; int p = 1; while(1){ int flag = 0; for(int i = 0; i < n; i++){ if(v[i].empty()){ flag = 1; v[i].push_back(p); p++; break; }else{ if(check(v[i].back(), p)){ v[i].push_back(p); flag = 1; p++; break; } } } if(!flag)break; } printf("%lld\n", p - 1); for(int i = 0; i < n; i++){ for(auto x : v[i]){ printf("%lld ", x); } printf("\n"); } return 0; }
题目描述
有 n 件工作要分配给 n 个人做。第 i 个人做第 j 件工作产生的效益为 cij 。试设计一个将 n 件工作分配给 n 个人做的分配方案,使产生的总效益最大。
输入格式
文件的第 1 行有 1 个正整数 n,表示有 n 件工作要分配给 n 个人做。
接下来的 n 行中,每行有 n 个整数 cij,表示第 i 个人做第 j 件工作产生的效益为 cij。
输出格式
两行分别输出最小总效益和最大总效益。
输入输出样例
输入:
5
2 2 2 1 2
2 3 1 2 4
2 0 1 1 1
2 3 4 3 3
3 2 1 2 1
输出:
5
14
思路:题意如上。做过最小费用最大流的同学看到这题狂喜,直接跑费用流求出最少效益,后重新建图将消耗取相反数,再跑一遍费用流,结果是最大效益的相反数。建图思路,源点s = 0,汇点 t = 2 * n + 1。先建s到员工点的边容量为1消耗为0意味着选这一个员工,再建员工到工件的边容量为1消耗为cij意味着一件工件只能由一个员工之能加工一件工件,最后建工件到t的边容量为1消耗为0意味着每件工件只能被一个员工加工。
#include <bits/stdc++.h> using namespace std; typedef unsigned long long ull; #define ll long long #define int long long const int maxn = 3e4 + 20; const int inf = 0x3f3f3f3f; const ll INF = 1ll << 62; const double eps = 1e-7; const int mod = 1e6 + 3; #define mem(a, b) memset(a, b, sizeof(a)) inline ll gcd(ll a, ll b) { return b == 0 ? a : gcd(b, a % b); } inline int rd() { int x = 0,f = 1;char ch = getchar(); while (!isdigit(ch)){if (ch == \'-\') f = -1;ch = getchar();} while (isdigit(ch)){x = x * 10 + ch - \'0\';ch = getchar();} return x * f; } void Put(ll x) { if (x < 0) putchar(\'-\'), x *= -1; if (x > 9) Put(x / 10); putchar(x % 10 + \'0\'); } int n, s, t; int head[maxn], cnt, cost[300][300]; struct Edge{ int u, v, w, c, nxt; }e[maxn]; void init(){ for(int i = s; i <= t; i++) head[i] = -1; cnt = 0; } void addEdge(int u, int v, int w, int c){ e[cnt].v = v; e[cnt].w = w; e[cnt].c = c; e[cnt].nxt = head[u]; head[u] = cnt++; } int pre[maxn], fa[maxn], inq[maxn], dis[maxn]; bool SPFA(){ for(int i = s; i <= t; i++){ pre[i] = -1; fa[i] = -1; inq[i] = 0; dis[i] = inf; } queue<int>q; q.push(s); dis[s] = 0; inq[s] = 1; pre[t] = -2; while(!q.empty()){ int u = q.front(); q.pop(); inq[u] = 0; for(int i = head[u]; ~i; i = e[i].nxt){ int v = e[i].v, w = e[i].w, c = e[i].c; if(w > 0 && dis[v] > dis[u] + c){ dis[v] = dis[u] + c; pre[v] = i; fa[v] = u; if(!inq[v]){ q.push(v); inq[v] = 1; } } } } return (pre[t] != -2); } int MCMF(){ int min_cost = 0; while(SPFA()){ int min_flow = inf; int p = t; while(p != s){ min_flow = min(min_flow, e[pre[p]].w); p = fa[p]; } min_cost += min_flow * dis[t]; p = t; while(p != s){ e[pre[p]].w -= min_flow; e[pre[p] ^ 1].w += min_flow; p = fa[p]; } } return min_cost; } signed main(){ int n = rd(); s = 0; t = n * n + 1; init(); for(int i = 1; i <= n; i++){ for(int j = n + 1; j <= n + n; j++){ cost[i][j] = rd(); } } for(int i = 1; i <= n; i++){ addEdge(s, i, 1, 0); addEdge(i, s, 0, 0); } for(int i = 1; i <= n; i++){ for(int j = n + 1; j <= n + n; j++){ addEdge(i, j, 1, cost[i][j]); addEdge(j, i, 0, -cost[i][j]); } } for(int i = n + 1; i <= n + n; i++){ addEdge(i, t, 1, 0); addEdge(t, i, 0, 0); } printf("%lld\n", MCMF()); init(); for(int i = 1; i <= n; i++){ addEdge(s, i, 1, 0); addEdge(i, s, 0, 0); } for(int i = 1; i <= n; i++){ for(int j = n + 1; j <= n + n; j++){ addEdge(i, j, 1, -cost[i][j]); addEdge(j, i, 0, cost[i][j]); } } for(int i = n + 1; i <= n + n; i++){ addEdge(i, t, 1, 0); addEdge(t, i, 0, 0); } printf("%lld\n", -MCMF()); return 0; }
题目描述
一共有 n 个飞行员,其中有 m 个外籍飞行员和 (n - m) 个英国飞行员,外籍飞行员从 1 到 m 编号,英国飞行员从 m + 1 到 n 编号。 对于给定的外籍飞行员与英国飞行员的配合情况,试设计一个算法找出最佳飞行员配对方案,使皇家空军一次能派出最多的飞机。
输入格式
输入的第一行是用空格隔开的两个正整数,分别代表外籍飞行员的个数 m 和飞行员总数 n。
从第二行起到倒数第二行,每行有两个整数 u, v,代表外籍飞行员 u 可以和英国飞行员 v 配合。
输入的最后一行保证为 -1 -1,代表输入结束。
输出格式
本题存在 Special Judge。
请输出能派出最多的飞机数量,并给出一种可行的方案。
输出的第一行是一个整数,代表一次能派出的最多飞机数量,设这个整数是 k。
第 2 行到第 k + 1行,每行输出两个整数 u, v代表在你给出的方案中,外籍飞行员 u 和英国飞行员 v 配合。这 k 行的 u 与 v 应该互不相同。
输入输出样例
输入:
5 10
1 7
1 8
2 6
2 9
2 10
3 7
3 8
4 7
4 8
5 10
-1 -1
输出:
4
1 7
2 9
3 8
5 10
思路:题意如上,水题。这是二分图匹配的模板题,在这里用最大流求最大匹配。建图,源点 s = 0, 汇点 t = n + 1。从s到外籍飞行员建边,从外籍飞行员到英籍飞行员建边,最后英籍飞行员到汇点建边,边的容量都为1,反边容量为0.
#include <bits/stdc++.h> using namespace std; typedef unsigned long long ull; #define ll long long #define int long long const int maxn = 2e4 + 20; const int inf = 0x3f3f3f3f; const ll INF = 1ll << 62; const double eps = 1e-7; const int mod = 1e6 + 3; #define mem(a, b) memset(a, b, sizeof(a)) inline ll gcd(ll a, ll b) { return b == 0 ? a : gcd(b, a % b); } inline int rd() { int x = 0,f = 1;char ch = getchar(); while (!isdigit(ch)){if (ch == \'-\') f = -1;ch = getchar();} while (isdigit(ch)){x = x * 10 + ch - \'0\';ch = getchar();} return x * f; } void Put(ll x) { if (x < 0) putchar(\'-\'), x *= -1; if (x > 9) Put(x / 10); putchar(x % 10 + \'0\'); } int m, n, s, t; int head[maxn], cnt; struct Edge{ int u, v, w, nxt; }e[maxn]; void init(){ for(int i = s; i <= t; i++) head[i] = -1; cnt = 0; } void addEdge(int u, int v, int w){ e[cnt].v = v; e[cnt].w = w; e[cnt].nxt = head[u]; head[u] = cnt++; } int deep[maxn]; bool bfs(){ for(int i = s; i <= t; i++){ deep[i] = 0; } queue<int>q; q.push(s); deep[s] = 1; while(!q.empty()){ int u = q.front(); q.pop(); for(int i = head[u]; ~i; i = e[i].nxt){ int v = e[i].v, w = e[i].w; if(w > 0 && deep[v] == 0){ deep[v] = deep[u] + 1; q.push(v); } } } return (deep[t] != 0); } int dinic(int u, int flow){ if(u == t)return flow; int ans = 0; for(int i = head[u]; ~i; i = e[i].nxt){ int v = e[i].v, w = e[i].w; if(w > 0 && deep[v] == deep[u] + 1){ int tmp = dinic(v, min(flow, w)); if(!tmp)deep[v] = 0; ans += tmp; flow -= tmp; e[i].w -= tmp; e[i ^ 1].w += tmp; } } return ans; } int maxFlow(){ int ans = 0; while(bfs()){ ans += dinic(s, inf); } return ans; } signed main(){ m = rd(); n = rd(); s = 0, t = n + 1; init(); for(int i = 1; i <= m; i++){ addEdge(s, i, 1); addEdge(i, s, 0); } int x, y; while(~scanf("%lld %lld", &x, &y), x != -1 && y != -1){ addEdge(x, y, 1); addEdge(y, x, 0); } for(int i = m + 1; i <= n; i++){ addEdge(i, t, 1); addEdge(t, i, 0); } printf("%lld\n", maxFlow()); for(int i = 1; i <= m; i++){ for(int j = head[i]; ~j; j = e[j].nxt){ int v = e[j].v, w = e[j].w; if(v > m && v <= n && w == 0){ printf("%lld %lld\n", i, v); } } } return 0; }
题目描述
给定有向图 G=(V,E)。设 P 是 G 的一个简单路(顶点不相交)的集合。如果 V 中每个定点恰好在P的一条路上,则称 P 是 G 的一个路径覆盖。P中路径可以从 V 的任何一个定点开始,长度也是任意的,特别地,可以为 0 。G 的最小路径覆盖是 G 所含路径条数最少的路径覆盖。设计一个有效算法求一个 DAG (有向无环图) G 的最小路径覆盖。
输入格式
第一行有 2 个正整数 n 和 m 。 n 是给定GAP(有向无环图) G 的顶点数, m 是 G 的边数。接下来的 m 行,每行有两个正整数 i 和 j 表示一条有向边 (i,j)。
输出格式
从第1 行开始,每行输出一条路径。文件的最后一行是最少路径数。
输入输出样例
输入:
11 12
1 2
1 3
1 4
2 5
3 6
4 7
5 8
6 9
7 10
8 11
9 11
10 11
输出:
2 5 8
4 7 10 11
6 9
3
思路:题意如上。计算最小路径覆盖就是通过顶点数减去最大匹配就是答案。我自己的难点在于路径的输出,可能是在这类问题做的题目太少。
#include <bits/stdc++.h> using namespace std; typedef unsigned long long ull; #define ll long long #define int long long const int maxn = 1e5 + 20; const int inf = 0x3f3f3f3f; const ll INF = 1ll << 62; const double eps = 1e-7; const int mod = 1e6 + 3; #define mem(a, b) memset(a, b, sizeof(a)) inline ll gcd(ll a, ll b) { return b == 0 ? a : gcd(b, a % b); } inline int rd() { int x = 0,f = 1;char ch = getchar(); while (!isdigit(ch)){if (ch == \'-\') f = -1;ch = getchar();} while (isdigit(ch)){x = x * 10 + ch - \'0\';ch = getchar();} return x * f; } void Put(ll x) { if (x < 0) putchar(\'-\'), x *= -1; if (x > 9) Put(x / 10); putchar(x % 10 + \'0\'); } int m, n, s, t; int head[maxn], cnt; int pre[maxn], to[maxn], vis[maxn]; struct Edge{ int u, v, w, nxt; }e[maxn]; void init(){ for(int i = s; i <= t; i++) head[i] = -1; cnt = 0; } void addEdge(int u, int v, int w){ e[cnt].u = u; e[cnt].v = v; e[cnt].w = w; e[cnt].nxt = head[u]; head[u] = cnt++; } int deep[maxn]; bool bfs(){ for(int i = s; i <= t; i++){ deep[i] = 0; } queue<int>q; q.push(s); deep[s] = 1; while(!q.empty()){ int u = q.front(); q.pop(); for(int i = head[u]; ~i; i = e[i].nxt){ int v = e[i].v, w = e[i].w; if(w > 0 && deep[v] == 0){ deep[v] = deep[u] + 1; q.push(v); } } } return (deep[t] != 0); } int dinic(int u, int flow){ if(u == t)return flow; int ans = 0; for(int i = head[u]; ~i; i = e[i].nxt){ int v = e[i].v, w = e[i].w; if(w > 0 && deep[v] == deep[u] + 1){ int tmp = dinic(v, min(flow, w)); if(!tmp)deep[v] = 0; ans += tmp; flow -= tmp; e[i].w -= tmp; e[i ^ 1].w += tmp; if(tmp){ // 标记后继和非头节点 to[u] = v; if(u != s)vis[v - n] = 1; } } } return ans; } int maxFlow(){ int ans = 0; while(bfs()){ int tmp = dinic(s, inf); ans += tmp; } return ans; } vector<int>vec[maxn]; signed main(){ n = rd(), m = rd(); s = 0, t = 2 * n + 1; init(); for(int i = 0; i < m; i++){ int u = rd(), v = rd(); vec[u].push_back(v); } for(int i = 1; i <= n; i++){ addEdge(s, i, 1); addEdge(i, s, 0); } for(int i = 1; i <= n; i++){ for(auto v: vec[i]){ addEdge(i, v + n, 1); addEdge(v + n, i, 0); } } for(int i = n + 1; i <= 2 * n; i++){ addEdge(i, t, 1); addEdge(t, i, 0); } int max_flow = maxFlow(); // 路径输出 for(int i = 1; i <= n; i++){ int u = i; if(!vis[u]){ printf("%lld", u); while(to[u] != t && to[u]){ printf(" %lld", to[u] - n); u = to[u] - n; } printf("\n"); } } cout << n - max_flow << endl; return 0; }
题目描述
一个餐厅在相继的 N 天里,每天需用的餐巾数不尽相同。假设第 i 天需要 ri块餐巾( i=1,2,...,N)。餐厅可以购买新的餐巾,每块餐巾的费用为 p 分;或者把旧餐巾送到快洗部,洗一块需 m 天,其费用为 f 分;或者送到慢洗部,洗一块需 nn 天(n>mn>m),其费用为 ss 分(s<fs<f)。
每天结束时,餐厅必须决定将多少块脏的餐巾送到快洗部,多少块餐巾送到慢洗部,以及多少块保存起来延期送洗。但是每天洗好的餐巾和购买的新餐巾数之和,要满足当天的需求量。
试设计一个算法为餐厅合理地安排好 N 天中餐巾使用计划,使总的花费最小。编程找出一个最佳餐巾使用计划。
输入格式
由标准输入提供输入数据。文件第 1 行有 1 个正整数 N,代表要安排餐巾使用计划的天数。
接下来的一行是餐厅在相继的 N 天里,每天需用的餐巾数。
最后一行包含5个正整数p,m,f,n,s。p 是每块新餐巾的费用; m 是快洗部洗一块餐巾需用天数; f 是快洗部洗一块餐巾需要的费用; n 是慢洗部洗一块餐巾需用天数; s 是慢洗部洗一块餐巾需要的费用。
输出格式
将餐厅在相继的 N 天里使用餐巾的最小总花费输出
输入输出样例
输入:
3
1 7 5
11 2 2 3 1
输出:
134
N <= 2000 ri <= 1e7 p,f,s <= 1e4 Time = 4s
思路:题意如上。这题的构图方式比较特殊,与我之前做的题构图有较大出路,但是这也提醒了我网络流的构图应当不局限于传统的构图。
首先,我们拆点,将一天拆成晚上和早上,每天晚上会受到脏餐巾(来源:当天早上用完的餐巾,在这道题中可理解为从原点获得),每天早上又有干净的餐巾(来源:购买、快洗店、慢洗店)。
1.从原点向每一天晚上连一条流量为当天所用餐巾x,费用为0的边,表示每天晚上从起点获得x条脏餐巾。
2.从每一天早上向汇点连一条流量为当天所用餐巾x,费用为0的边,每天白天,表示向汇点提供x条干净的餐巾,流满时表示第i天的餐巾够用 。
3.从每一天晚上向第二天晚上连一条流量为INF,费用为0的边,表示每天晚上可以将脏餐巾留到第二天晚上(注意不是早上,因为脏餐巾在早上不可以使用)。
4.从每一天晚上向这一天+快洗所用天数t1的那一天早上连一条流量为INF,费用为快洗所用钱数的边,表示每天晚上可以送去快洗部,在地i+t1天早上收到餐巾 。
5.同理,从每一天晚上向这一天+慢洗所用天数t2的那一天早上连一条流量为INF,费用为慢洗所用钱数的边,表示每天晚上可以送去慢洗部,在地i+t2天早上收到餐巾 。
6.从起点向每一天早上连一条流量为INF,费用为购买餐巾所用钱数的边,表示每天早上可以购买餐巾 。 注意,以上6点需要建反向边!3~6点需要做判断(即连向的边必须<=n)
思路来源:Mark_ZZY
#include <bits/stdc++.h> using namespace std; typedef unsigned long long ull; #define ll long long #define int long long const int maxn = 4e3 + 20; const int inf = 0x3f3f3f3f; const ll INF = 1ll << 62; const double eps = 1e-8; const int mod = 1e6 + 3; #define mem(a, b) memset(a, b, sizeof(a)) inline ll gcd(ll a, ll b) { return b == 0 ? a : gcd(b, a % b); } inline int rd() { int x = 0,f = 1;char ch = getchar(); while (!isdigit(ch)){if (ch == \'-\') f = -1;ch = getchar();} while (isdigit(ch)){x = x * 10 + ch - \'0\';ch = getchar();} return x * f; } void Put(ll x) { if (x < 0) putchar(\'-\'), x *= -1; if (x > 9) Put(x / 10); putchar(x % 10 + \'0\'); } inline double rdd(){ double x; scanf("%lf", &x); return x; } int n, s, t; int head[maxn], cnt; int pre[maxn], fa[maxn], inq[maxn], dis[maxn]; int ned[maxn], pp, mm, ff, nn, ss; struct Edge{ int v, w, c, nxt; }e[maxn * 100]; void init(){ for(int i = s; i <= t; i++){ head[i] = -1; } cnt = 0; } void addEdge(int u, int v, int w, int c){ e[cnt].v = v; e[cnt].w = w; e[cnt].c = c; e[cnt].nxt = head[u]; head[u] = cnt++; } bool SPFA(){ for(int i = s; i <= t; i++){ pre[i] = fa[i] = -1; inq[i] = 0; dis[i] = inf; } queue<int>q; while(!q.empty())q.pop(); q.push(s); dis[s] = 0; inq[s] = 1; pre[t] = -2; while(!q.empty()){ int u = q.front(); q.pop(); inq[u] = 0; for(int i = head[u]; ~i; i = e[i].nxt){ int v = e[i].v, w = e[i].w, c = e[i].c; if(w > 0 && dis[v] > dis[u] + c){ dis[v] = dis[u] + c; pre[v] = i; fa[v] = u; if(!inq[v]){ q.push(v); inq[v] = 1; } } } } return (pre[t] != -2); } int MCMF(){ int min_cost = 0; while(SPFA()){ int min_flow = inf; int p = t; while(p != s){ min_flow = min(min_flow, e[pre[p]].w); p = fa[p]; } min_cost += min_flow * dis[t]; p = t; while(p != s){ e[pre[p]].w -= min_flow; e[pre[p] ^ 1].w += min_flow; p = fa[p]; } } return min_cost; } signed main(){ int n = rd(); s = 0, t = 2 * n + 1; init(); for(int i = 1; i <= n; i++) ned[i] = rd(); pp = rd(), mm = rd(), ff = rd(), nn = rd(), ss = rd(); // 建立源点到晚上点的边,意味着获取脏毛巾 for(int i = 1; i <= n; i++){ addEdge(s, i, ned[i], 0); addEdge(i, s, 0, 0); } //建立早上到汇点的边,意味着获取干净毛巾 for(int i = n + 1; i <= 2 * n; i++){ addEdge(i, t, ned[i - n], 0); addEdge(t, i, 0, 0); } //建立源点到早上点的边,意味着购买新的毛巾 for(int i = n + 1; i <= 2 * n; i++){ addEdge(s, i, inf, pp); addEdge(i, s, 0, -pp); } //建立每个点与其后一点的边,意味着可以将今天的脏毛巾留到后一天 for(int i = 1; i <= n - 1; i++){ addEdge(i, i + 1, inf, 0); addEdge(i + 1, i, 0, 0); } for(int i = 1; i <= n; i++){ //毛巾拿到快洗店去洗,意味着过了mm天才能用,加边从送出脏毛巾到获得干净毛巾 int v = i + mm + n; if(v <= 2 * n){ addEdge(i, v, inf, ff); addEdge(v, i, 0, -ff); } //毛巾拿到慢洗店去洗,意味着过了nn天才能用,加边从送出脏毛巾到获得干净毛巾 v = i + nn + n; if(v <= 2 * n){ addEdge(i, v, inf, ss); addEdge(v, i, 0, -ss); } } printf("%lld\n", MCMF()); return 0; }
未完待续...