莞中 2022暑假训练题01 最小费用最大流
T1 UVA1112 Mice and Maze
【题意】
给出 \(n(n<=100)\) 个点和 \(m\) 条边的有向图,给出终点 \(E\),问有几个结点到达终点的最短路<=T?
【思路】
因此在添加边关系时,将终点看成起点,则是明显的单源最短路问题。点数很小,用Floyd也可以。求出单源最短路后,统计有几个结点的最短路 \(<=T\) 即可。
PS:注意格式!!!
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1010, INF = 0x3f3f3f3f;
struct Edge
{
int v, w;
bool operator<(const Edge &e) const
{
return w > e.w;
}
};
list<Edge> edges[N];
int dis[N], pre[N], st[N];
int n, m, E, T;
void dijkstra(int u)
{
for (int i = 1; i <= n; i++) dis[i] = INF, pre[i] = -1, st[i] = 0;
dis[u] = 0;
while (!st[u])
{
st[u] = 1;
for (auto e : edges[u])
if (!st[e.v] && dis[e.v] > dis[u] + e.w)
dis[e.v] = dis[u] + e.w, pre[e.v] = u;
int minn = INF;
for (int i = 1; i <= n; i++)
if (!st[i] && minn > dis[i])
minn = dis[i], u = i;
}
}
int main()
{
int t;
cin >> t;
for (int c = 1; c <= t; c++)
{
cin >> n >> E >> T >> m;
for (int i = 1; i <= n; i++) edges[i].clear();
int u, v, w;
for (int i = 1; i <= m; i++)
{
cin >> u >> v >> w;
edges[v].push_back(Edge(u, w));
}
dijkstra(E);
int mice = 0;
for (int i = 1; i <= n; i++) if (dis[i] <= T) mice++;
if (c > 1) cout << '\n';
cout << mice << '\n';
}
return 0;
}
T2 UVA10986 Sending email
【题意】
带非负边权的无向图,点数<=2万,边数<=5万。给出两个点s和t,问从s到t的最短距离。有多组数据。
【思路】
最短路。点数边数比较大,用SPFA或者dijkstra+堆优化(因为本题边权非负)。注意有多组数据,相关状态数组记得初始化。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 20010, INF = 0x3f3f3f3f;
struct Edge
{
int v, w;
bool operator<(const Edge &e) const
{
return w > e.w;
}
};
list<Edge> Edges[N];
int dist[N], parent[N], visited[N];
int n, m, S, T;
void dijkstra(int u)
{
for (int i = 0; i < n; i++) dist[i] = INF, parent[i] = -1;
dist[u] = 0;
priority_queue<Edge> q;
q.push({u, dist[u]});
while (!q.empty())
{
Edge v = q.top();
q.pop();
for (auto e : Edges[v.v])
if (dist[e.v] > dist[v.v] + e.w)
{
dist[e.v] = dist[v.v] + e.w;
parent[e.v] = v.v;
q.push({e.v, dist[e.v]});
}
}
}
int main()
{
int t;
cin >> t;
for(int c=1;c<=t;c++)
{
int u, v, w;
cin >> n >> m >> S >> T;
for (int i = 0; i < n; i++) Edges[i].clear();
for (int i = 0; i < m; i++)
{
cin >> u >> v >> w;
Edges[u].push_back({v, w});
Edges[v].push_back({u, w});
}
dijkstra(S);
cout << "Case #" << c << ": ";
if (dist[T] != INF) cout << dist[T];
else cout << "unreachable";
cout << '\n';
}
return 0;
}
T3 HDU4289 Control
【题意】
给出一个无向图,犯罪分子要从起点运东西到终点,封锁一个城市要相应的费用,求最小费用
【思路】
开始时定式思维以为是费用流,其实只是一个普通的求最小割。将城市拆成两个点,之间连一条容量为费用的边。对于无向图中的每一条边,从 u 到 v' 连一条边,从 v 到 u' 连一条边。跑dinic即可。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1005, M = 50000, INF = 0x3f3f3f3f;
int n, m;
int start, dest;
int head[N];
struct Edge
{
int v,nxt;
int w;
} e[M*2];
int idx = 1;
void add(int u,int v,int w)
{
e[++idx].v = v, e[idx].w = w, e[idx].nxt = head[u], head[u] = idx;
}
int dep[N];
int now[N];
bool bfs()
{
memset(dep,0x3f,sizeof dep);
queue<int> que;
que.push(start);
dep[start] = 0;
now[start] = head[start];
while(!que.empty())
{
int u = que.front();
que.pop();
for(int i=head[u];i;i=e[i].nxt)
{
int v = e[i].v, w = e[i].w;
if(w > 0 && dep[v] == INF)
{
que.push(v);
now[v] = head[v];
dep[v] = dep[u] + 1;
if(v == dest) return 1;
}
}
}
return 0;
}
int dfs(int u,int sum)
{
if(u == dest) return sum;
int k = 0, res = 0;
for(int i=now[u];i && sum;i=e[i].nxt)
{
now[u] = i;
int v = e[i].v;
int w=e[i].w;
if(w > 0 && dep[v] == dep[u] + 1)
{
k = dfs(v,min(sum, w));
if(k == 0) dep[v] = INF;
e[i].w -= k;
e[i ^ 1].w += k;
res += k;
sum -= k;
}
}
return res;
}
int main()
{
while(cin>>n>>m)
{
idx=1;
memset(head,0,sizeof head);
memset(now,0,sizeof now);
scanf("%d%d",&start,&dest);
dest += n;
for(int i=1;i<=n;i++)
{
int c;
scanf("%d",&c);
add(i,i+n,c), add(i+n,i,0);
}
for(int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(v+n,u,INF), add(u,v+n,0);
add(u+n,v,INF), add(v,u+n,0);
}
int ans = 0;
while(bfs()) ans += dfs(start,INF);
cout<<ans<<endl;
}
return 0;
}
T4 HDU2732 Leapin' Lizards
【思路】
每次蜥蜴跳出一个位置之后这个位置的“资源”就会减少1,而这个减少之后的“资源”不可以再利用,而且涉及到点之间的转移,这在图论中和最大流很相似,所以我们想到可不可以将点转化成状态。地图中的每一个位置(i,j)(0<=i,j<n)可以通过i×m+j+1转化成[1,n×m]闭区间之内的位置(哈希),所以我们可以把这个设为他的id,可以将点位置拆开成两个,一个是id,表示没有转移时的状态,另一个点是id+n×m,表示这个点拆开之后的另一个状态。设置一个超级源和一个超级汇。
建图方法如下:
① 如果一个点位置(i,j)值num(表示可以承受的跳跃次数)大于0,那么可以连边(id,id+n×m,num),表示最多有num次“流”在这个位置转换,num个用完也就代表不能发生转换,也就是这个位置不能站蜥蜴了。
② 如果一个位置通过跳跃d可以到达外围就设置边(id+n×m,T,inf),就是把这个位置和超级汇连接起来,并且不限制从这里通过的蜥蜴的数量。
③ 如果一个位置有蜥蜴,我们可以把这个位置和超级源连接在一起,也就是(S,id,1),1表示在这个位置有一只蜥蜴出发。
④ 两个位置之间的距离(横纵坐标绝对值之差)如果小于d那就可以转换,注意连的边是(id+n×m,id2,1),因为在蜥蜴跳出当前位置时必须当前位置的承受容量减一。
建图后跑一遍最大流。
【【注意事项】
① 每个蜥蜴可以跳跃到与当前所在点曼哈顿距离<=d的地方,而不是上下左右距离<=d的地方。
② 输出是有几只蜥蜴跑不掉,0要输出no,还要考虑英文的单复数和第三人称单数。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 3005, M = 50000, INF = 0x3f3f3f3f;
int n, m, d;
int start, dest;
int head[N];
struct Edge
{
int v,nxt;
int w;
} e[M*2];
int idx = 1;
void add(int u,int v,int w)
{
e[++idx].v = v, e[idx].w = w, e[idx].nxt = head[u], head[u] = idx;
}
int dep[N];
int now[N];
bool bfs()
{
memset(dep,0x3f,sizeof dep);
queue<int> que;
que.push(start);
dep[start] = 0;
now[start] = head[start];
while(!que.empty())
{
int u = que.front();
que.pop();
for(int i=head[u];i;i=e[i].nxt)
{
int v = e[i].v, w = e[i].w;
if(w > 0 && dep[v] == INF)
{
que.push(v);
now[v] = head[v];
dep[v] = dep[u] + 1;
if(v == dest) return 1;
}
}
}
return 0;
}
int dfs(int u,int sum)
{
if(u == dest) return sum;
int k = 0, res = 0;
for(int i=now[u];i && sum;i=e[i].nxt)
{
now[u] = i;
int v = e[i].v;
int w=e[i].w;
if(w > 0 && dep[v] == dep[u] + 1)
{
k = dfs(v,min(sum, w));
if(k == 0) dep[v] = INF;
e[i].w -= k;
e[i ^ 1].w += k;
res += k;
sum -= k;
}
}
return res;
}
char g[25][25], gg[25][25];
int id(int i,int j) { return (i-1) * m + j; }
int dist(int i1,int j1,int i2,int j2) {return abs(i1-i2) + abs(j1-j2); }
int main()
{
int cases;
scanf("%d",&cases);
for(int c=1;c<=cases;c++)
{
idx=1;
memset(head,0,sizeof head);
memset(now,0,sizeof now);
scanf("%d%d",&n,&d);
for(int i=1;i<=n;i++) scanf("%s", g[i]+1);
m = strlen(g[1]+1);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(g[i][j] != '0')
add(id(i,j),n*m+id(i,j),g[i][j]-'0'), add(n*m+id(i,j),id(i,j),0);
for(int i=1;i<=n;i++) scanf("%s", gg[i]+1);
start = 2 * n * m + 1, dest = start + 1;
int lizards = 0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(gg[i][j] == 'L') add(start,id(i,j),1),add(id(i,j),start,0),lizards++;
if(i-d<1 || i+d>n || j-d<1 || j+d>m) add(id(i,j)+n*m,dest,INF),add(dest,id(i,j)+n*m,0);
else
{
for(int x=i-d;x<=i+d;x++)
for(int y=j-d;y<=j+d;y++)
if(g[x][y] != '0' && abs(i-x) + abs(j-y) <= d)
add(n*m+id(i,j), id(x,y), INF), add(id(x,y),n*m+id(i,j),0);
}
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int x=i-d;x<=i+d;x++)
for(int y=j-d;y<=j+d;y++)
{
if(x<1 || x>n || y<1 || y>m || g[x][y] == '0' || abs(i-x) + abs(j-y) > d) continue;
add(n*m+id(i,j), id(x,y), INF), add(id(x,y),n*m+id(i,j),0);
}
int ans = 0;
while(bfs()) ans += dfs(start,INF);
lizards -= ans;
printf("Case #%d: ",c);
if(lizards == 0) printf("no lizard was left behind.\n");
if(lizards == 1) printf("1 lizard was left behind.\n");
if(lizards > 1) printf("%d lizards were left behind.\n",lizards);
}
return 0;
}
T5 POJ2516 Minimum Cost
【题意】
有N个店主,M个供应商,K种物品。每个供应商对每种物品的的供应量和每个店主对每种物品的需求量的已知。不同的供应商运送不同的货物到不同的店主需要不同的花费。已知从供应商j送第k种货物的单位数量到店主i手上所需的单位花费。
问:供应是否满足需求?如果满足,输出最小花费。如果不满足,输出-1。
【思路】
对于每一种商品建图跑费用流。
超级源点向供应点连边,容量为INF,费用为0
供应点向店主连边,容量为供应点的供应量,费用为运输费用
店主向超级汇点连边,容量为INF,费用为0
点击查看代码
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int N = 6005, T = 55, M = 250005;
const int INF = 0x3f3f3f3f;
int n, m, K, s, t;
struct Node
{
int u,v,w,c,nxt;
} e[M*2];
int head[N], idx = 1;
void add(int u,int v,int w,int c)
{
e[++idx].v = v, e[idx].w = w, e[idx].c = c, e[idx].u = u, e[idx].nxt = head[u], head[u] = idx;
}
int flow,cost;
int pre[N], dis[N];
bool st[N];
bool spfa()
{
memset(st,0,sizeof st);
memset(dis, 0x3f, sizeof dis);
queue<int> q;
q.push(s);
dis[s] = 0, st[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, c = e[i].c;
if(e[i].w && dis[v] > dis[u] + c)
{
dis[v] = dis[u] + c;
pre[v] = i;
if(!st[v])
{
st[v] = 1;
q.push(v);
}
}
}
st[u] = 0;
}
if(dis[t] == INF) return 0;
else return 1;
}
int mcmf()
{
int minn = INF;
for(int i=pre[t];i;i=pre[e[i].u]) minn = min(minn, e[i].w);
flow += minn;
for(int i=pre[t];i;i=pre[e[i].u]) e[i].w -= minn, e[i^1].w += minn, cost += e[i].c * minn;
}
int need[T][T], supply[T][T], tran[T][T][T];
int main()
{
while(1)
{
flow = 0, cost = 0;
scanf("%d%d%d",&n,&m,&K);
if(n == 0 && m == 0 && K == 0) break;
int sum = 0;
for(int i=1;i<=n;i++)
for(int j=1;j<=K;j++)
scanf("%d",&need[i][j]), sum += need[i][j];
for(int i=1;i<=m;i++)
for(int j=1;j<=K;j++)
scanf("%d",&supply[i][j]);
s = 6000, t = s + 1;
for(int j=1;j<=K;j++)
{
for(int i=1;i<=n;i++)
for(int k=1;k<=m;k++)
scanf("%d",&tran[j][i][k]);
idx = 1;
memset(pre,0,sizeof pre);
memset(head,0,sizeof head);
for(int i=1;i<=m;i++)
add(s,i,supply[i][j],0), add(i,s,0,0);
for(int k=1;k<=n;k++)
for(int i=1;i<=m;i++)
add(i,k+m,supply[i][j],tran[j][k][i]), add(k+m,i,0,-tran[j][k][i]);
for(int i=1;i<=n;i++)
add(i+m,t,need[i][j],0), add(t,i+m,0,0);
while(spfa()) mcmf();
}
if(sum != flow) printf("-1\n");
else printf("%d\n",cost);
}
return 0;
}
T6 HDU1533 Going Home
【题意】
给出一个 n * m 的矩阵,里面有若干个小人和房子,保证小人和房子的数量相同。
要求每一个小人都进入一个房子,费用为两者之间的曼哈顿距离。
【思路】
将小人和源点连边,容量为1,费用为0
将所有的小人和所有的房子连边,容量为1,费用为两者之间的曼哈顿距离
将所有的房子和汇点连边,容量为1,费用为0
点击查看代码
#pragma GCC optimize(3)
#pragma GCC optimize("inline")
#pragma GCC optimize("Ofast")
#pragma GCC target("sse3","sse2","sse")
#pragma GCC diagnostic error "-std=c++14"
#pragma GCC diagnostic error "-fwhole-program"
#pragma GCC diagnostic error "-fcse-skip-blocks"
#pragma GCC diagnostic error "-funsafe-loop-optimizations"
#pragma GCC optimize("fast-math","unroll-loops","no-stack-protector","inline")
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
const int N = 10005;
const int INF = 0x3f3f3f3f;
int n, m, s, t;
struct Node
{
int u,v,w,c,nxt;
} e[N*8];
int head[N], idx = 1;
void add(int u,int v,int w,int c)
{
e[++idx].v = v, e[idx].w = w, e[idx].c = c, e[idx].u = u, e[idx].nxt = head[u], head[u] = idx;
}
int flow,cost;
int pre[N], dis[N];
bool st[N];
bool spfa()
{
memset(st,0,sizeof st);
memset(dis,0x3f,sizeof dis);
queue<int> q;
q.push(s);
dis[s] = 0, st[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, c = e[i].c;
if(e[i].w && dis[v] > dis[u] + c)
{
dis[v] = dis[u] + c;
pre[v] = i;
if(!st[v])
{
st[v] = 1;
q.push(v);
}
}
}
st[u] = 0;
}
if(dis[t] == INF) return 0;
else return 1;
}
int mcmf()
{
int minn = INF;
for(int i=pre[t];i;i=pre[e[i].u]) minn = min(minn, e[i].w);
flow += minn;
for(int i=pre[t];i;i=pre[e[i].u]) e[i].w -= minn, e[i^1].w += minn, cost += e[i].c * minn;
}
char g[105][105];
struct Pair
{
int x, y, id;
}P[105], H[105];
int pp, hh;
int main()
{
while(cin>>n>>m)
{
if(n==0&&m==0) break;
idx=1;
memset(head,0,sizeof head);
memset(pre,0,sizeof pre);
memset(e,0,sizeof e);
flow = 0, cost = 0;
pp = 0, hh = 0;
s = n * m + 1,t = n * m + 2;
for(int i=1;i<=n;i++) scanf("%s",g[i]+1);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
int p = (i-1) * m + j;
if(g[i][j] == 'm') P[++pp].x = i, P[pp].y=j, P[pp].id = p, add(s, p, 1, 0), add(p, s, 0, 0);
if(g[i][j] == 'H') H[++hh].x = i, H[hh].y=j, H[hh].id = p, add(p, t, 1, 0), add(t, p ,0, 0);
}
for(int i=1;i<=pp;i++)
for(int j=1;j<=hh;j++)
add(P[i].id, H[j].id, 1, abs(P[i].x - H[j].x) + abs(P[i].y - H[j].y)),
add(H[j].id, P[i].id, 0, -(abs(P[i].x - H[j].x) + abs(P[i].y - H[j].y)));
while(spfa()) mcmf();
cout<<cost<<endl;
}
return 0;
}
T7 luogu P2153 [SDOI2009]晨跑
【题意】
给出一个无向图
在满足一个点只能经过一次的情况下,求最多有多少条1~n的路径和这些路径的长度最小值。
【思路】
拆点,\(u\) 和 \(u'\) 之间连一条容量为 \(1\),费用为 \(0\) 的边。
对于原来的边,在 \(u\) 和 \(v'\),\(v\) 和 \(u'\) 连一条容量为 \(1\),费用为 \(w\) 的边。
由于 \(1\) 点和 \(n\) 点可以经过多次,所以超级源点连向 \(1'\),超级汇点连向 \(n\),容量为 \(\text{INF}\),费用为 \(0\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 10005, M = 5e4 + 10;
const int INF = 0x3f3f3f3f;
int n, m, s, t;
struct Node
{
int u,v,w,c,nxt;
} e[M*2];
int head[N], idx = 1;
void add(int u,int v,int w,int c)
{
e[++idx].v = v, e[idx].w = w, e[idx].c = c, e[idx].u = u, e[idx].nxt = head[u], head[u] = idx;
}
int flow,cost;
int pre[N], dis[N];
bool st[N];
bool spfa()
{
memset(st,0,sizeof st);
memset(dis, 0x3f, sizeof dis);
queue<int> q;
q.push(s);
dis[s] = 0, st[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, c = e[i].c;
if(e[i].w && dis[v] > dis[u] + c)
{
dis[v] = dis[u] + c;
pre[v] = i;
if(!st[v])
{
st[v] = 1;
q.push(v);
}
}
}
st[u] = 0;
}
if(dis[t] == INF) return 0;
else return 1;
}
int mcmf()
{
int minn = INF;
for(int i=pre[t];i;i=pre[e[i].u]) minn = min(minn, e[i].w);
flow += minn;
for(int i=pre[t];i;i=pre[e[i].u]) e[i].w -= minn, e[i^1].w += minn, cost += e[i].c * minn;
}
int T[100][100];
int main()
{
scanf("%d%d",&m,&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&T[i][j]);
s = 10000, t = 10001;
for(int i=1;i<=n;i++) add(s,i,1,0), add(i,s,0,0);
for(int i=n+1;i<=n+n*m;i++) add(i,t,1,0), add(t,i,0,0);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int k=1;k<=n;k++)
add(i,n+(k-1)*m+j,1,k*T[i][j]), add(n+(k-1)*m+j,i,0,-k*T[i][j]);
while(spfa()) mcmf();
printf("%.2lf\n",1.0*cost/n);
return 0;
}
T8
【题意】
同一时刻有 \(N\) 位车主带着他们的爱车来到了汽车维修中心。
维修中心共有 \(M\) 位技术人员,不同的技术人员对不同的车进行维修所用的时间是不同的。
现在需要安排这 \(M\) 位技术人员所维修的车及顺序,使得顾客平均等待的时间最小。
【思路】
把 \(n\) 和 \(m\) 调换一下,符合习惯。
把每个工人拆成 \(n\) 个点。记为 \(a[i,j]\) 表示第 \(i\) 个工人修倒数第 \(j\) 辆车。
每个车跟所有 \(n \times m\) 个工人拆出的点连边。流量为 \(1\) ,费用为 \(time[i,j] \times k\) 。
源和每辆车连边, \(n\times m\) 个点和汇连边,流量都为 \(1\) ,费用为 \(0\) 。