Minimum Cost
原题链接
题目类型:最小费用最大流
对于每个供应商和每个进货者,都设置了K个点,从而加上源点和汇点,总共有NK+MK+2个点,导致超时。
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
#define INF 0x3f3f3f3f
#define NMAX 55
#define MAXDOT 2*NMAX * NMAX+10
int N, M, K;
struct edge{
int from, to, cap, flow,cost;
};
vector<edge> E;
vector<int> G[MAXDOT];//最多可能含有的点数
//最小费用最大流所需要的变量
int inq[MAXDOT] = { 0 };
int dis[MAXDOT];
int cflow[MAXDOT];
int pre[MAXDOT] = { 0 };
int Dot = 0;
void Addedge(int s, int t, int cap, int cost) {
struct edge ec;
ec.from = s, ec.to = t, ec.cap = cap, ec.flow = 0, ec.cost = cost;
E.push_back(ec);
ec.from = t, ec.to = s, ec.cap = 0, ec.flow = 0, ec.cost = -cost;
E.push_back(ec);
int m = E.size();
G[s].push_back(m - 2);
G[t].push_back(m - 1);
}
int SPFA(int s, int d, int& cost, int& flow) {
for (int i = 0; i <= Dot; i++) inq[i] = 0, cflow[i] = INF, dis[i] = INF;
queue<int > Q;
Q.push(s);
inq[s] = 1;
dis[s] = 0;
while (!Q.empty()) { //注意条件是非空
int cs = Q.front();
Q.pop();
inq[cs] = 0;
for (int i = 0; i < G[cs].size(); i++) {
int num = G[cs][i];
int cd = E[num].to;
if (E[num].cap > E[num].flow) {
if (dis[cs] + E[num].cost < dis[cd]) {
dis[cd] = dis[cs] + E[num].cost; //这里加号后面时dis[cs]
cflow[cd] = min(cflow[cs], E[num].cap - E[num].flow);
pre[cd] = num;
if (!inq[cd]) {
Q.push(cd);
inq[cd] = 1;
}
}
}
}
}
if (dis[d] == INF) return 0;
flow += cflow[d];
cost += cflow[d] * dis[d];
int cur = d;
while (cur != s) {
int num = pre[cur];
E[num].flow += cflow[d];
E[num ^ 1].flow -= cflow[d];
cur = E[num].from;
}
return 1;
}
int Mincost(int s, int t, int test) {
int cost = 0, flow = 0;
while (SPFA(s, t, cost, flow));
if (flow != test) return -1;
return cost;
}
int main() {
while (1) {
int test = 0;
cin >> N >> M >> K;
if (!N && !M && !K) break;
//初始化
Dot = 1;
E.clear();
for (int i = 0; i <= N * K + M * K + 1; i++) {
G[i].clear();
//标号0代表源点,标号N * K + M * K + 1代表汇点
}
int s = 0, d = N * K + M * K + 1;
for (int i = 0; i < N; i++) {
for (int j = 0; j < K; j++) {
int cur;
cin >> cur;
test += cur;
Addedge(Dot, d, cur,0);
Dot++;
}
}
cout << Dot << endl;
for (int i = 0; i < M; i++) {
for (int j = 0; j < K; j++) {
int cur;
cin >> cur;
Addedge(0, Dot, cur,0);
Dot++;
}
}
cout << Dot << endl;
for (int i = 1; i <= K; i++) {
//K个矩阵
for (int j = 0; j < N; j++) {
for (int k = 0; k < M; k++) {
int cur;
cin >> cur;
Addedge(k * K + i + N * K, j * K + i, INF, cur);
}
}
}
// Dot++;
/*for (int i = 0; i < E.size(); i++) {
cout << E[i].from << " " << E[i].to << " " << E[i].cap << " " << E[i].cost << endl;
}*/
if (Dot != d) cout << "count error";
cout << Mincost(s, d, test) << endl;
}
}
为此,不难发现,第i个商品的供应关系和第j个商品的供应关系是无联系的,因此,可以分别进行k次求解,每次都求出满足第i个商品供应关系的最小费用,最后再进行叠加即可。
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
#define INF 0x3f3f3f3f
#define NMAX 55
#define MAXDOT 2*NMAX * NMAX+10
int N, M, K;
struct edge{
int from, to, cap, flow,cost;
};
vector<edge> E;
vector<int> G[MAXDOT];//最多可能含有的点数
int shop[NMAX][NMAX];
int supply[NMAX][NMAX];
//最小费用最大流所需要的变量
int inq[MAXDOT] = { 0 };
int dis[MAXDOT];
int cflow[MAXDOT];
int pre[MAXDOT] = { 0 };
int Dot = 0;
void Addedge(int s, int t, int cap, int cost) {
struct edge ec;
ec.from = s, ec.to = t, ec.cap = cap, ec.flow = 0, ec.cost = cost;
E.push_back(ec);
ec.from = t, ec.to = s, ec.cap = 0, ec.flow = 0, ec.cost = -cost;
E.push_back(ec);
int m = E.size();
G[s].push_back(m - 2);
G[t].push_back(m - 1);
}
int SPFA(int s, int d, int& cost, int& flow) {
for (int i = 0; i <= Dot; i++) inq[i] = 0, cflow[i] = INF, dis[i] = INF;
queue<int > Q;
Q.push(s);
inq[s] = 1;
dis[s] = 0;
while (!Q.empty()) { //注意条件是非空
int cs = Q.front();
Q.pop();
inq[cs] = 0;
/*if (cs == 1|| cs==7 || cs==0) {
cout << "test";
}*/
for (int i = 0; i < G[cs].size(); i++) {
int num = G[cs][i];
int cd = E[num].to;
/*if (cd == 7) {
cout << "test";
}*/
if (E[num].cap > E[num].flow) {
if (dis[cs] + E[num].cost < dis[cd]) {
dis[cd] = dis[cs] + E[num].cost; //这里加号后面时dis[cs]
cflow[cd] = min(cflow[cs], E[num].cap - E[num].flow);
pre[cd] = num;
if (!inq[cd]) {
Q.push(cd);
inq[cd] = 1;
}
}
}
}
}
if (dis[d] == INF) return 0;
flow += cflow[d];
cost += cflow[d] * dis[d];
int cur = d;
while (cur != s) {
int num = pre[cur];
E[num].flow += cflow[d];
E[num ^ 1].flow -= cflow[d];
cur = E[num].from;
}
return 1;
}
int Mincost(int s, int t, int test, int k) {
if (k) return -1;
int cost = 0, flow = 0;
while (SPFA(s, t, cost, flow));
if (flow != test) return -1;
return cost;
}
int main() {
while (1) {
cin >> N >> M >> K;
if (!N && !M && !K) break;
//初始化
Dot = N + M + 1;
int s = 0, d = N + M + 1;
int cost = 0, key = 0;
for (int i = 0; i < N; i++) {
for (int j = 0; j < K; j++) {
cin >> shop[i][j];
}
}
for (int i = 0; i < M; i++) {
for (int j = 0; j < K; j++) {
cin >> supply[i][j];
}
}
for (int i = 0; i < K; i++) {
//清空构造的图
int test = 0;
E.clear();
for (int i = 0; i <= Dot; i++) {
G[i].clear();
//标号0代表源点,标号Dot代表汇点
//供应商为1-M,进货商为M+1- M+N
}
for (int j = 0; j < M; j++) Addedge(0, j + 1, supply[j][i], 0);
for (int j = 0; j < N; j++) {
Addedge(M + 1 + j, d, shop[j][i], 0);
test += shop[j][i];
}
for (int j = M+1; j <= M+N; j++) {
for (int k = 1; k <= M; k++) {
int cur;
cin >> cur;
Addedge(k, j , INF, cur);
}
}
int res = Mincost(s, d, test, key);
if (res == -1) {
key = 1;
}
else cost += res;
}
if (key) cout << -1 << endl;
else cout << cost << endl;
}
}
Paratroopers(poj 3308)
原题链接
题目类型:二分图的最小点权覆盖、最小割
什么是点覆盖集呢?就是图中所有点的一个子集,首先他是一个点集,然后图中所有边的两个端点的其中一个都在这个点集中,就是说这个点集中包含了所有边的至少一个端点,这个点集就覆盖了所有边。那么对于每个点我们给他一个权值,所有点覆盖集中,总权值和最小的一个就是所说的最小权点覆盖集。求解最小权点覆盖集是较难的,但是对于二分图而言,却有特别的方式。
由二分图的定义可知,可以将二分图的点集分为X集和Y集,从源点s建立到X集中每一个点的边,边的权值即为点的权值,同理,建立Y集到汇点d的边。对于二分图中原有的边,设置其权值为无穷大。于是,获得了新建立的网络流图G。
对于X和Y中点的选择,也就转化为对于边SXi和边SYj的选择。
那么也就是说,我们要选择边SXi或者SYj,来覆盖二分图中原有的边(问题2)。
然而,任意的一个割,若其满足所有割边都包含s或者d,那么能覆盖所有的中间边。如下图所示的一个边xy,若未被覆盖,则sx和yd都不是割边,那么s和d属于同一个集合,与割的定义矛盾。因此,任意的一个割,能覆盖所有的边。
s--------x---------y----------d
又由于最小割中的割边一定不可能包含xy(因为xy的容量是INF),因此,便可以将问题2转化为求最小割。
又最小割等于最大流,因此,将上述问题转化为求解图G的最大流。
同时需要注意的是,该题是要求相乘的最大值,可以利用log转化为相加的最大值。
但是不知道为什么WA
#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
#include<cmath>
using namespace std;
#define NMMAX 60
#define INF 1e8
int T;
int m, n, l;
//dinic相关
struct edge {
int from, to;
double cap, flow;
edge(int u, int v, double c, double f) : from(u), to(v), cap(c), flow(f) {}
};
vector<edge> edges;
vector<int> G[2 * NMMAX];//链式前向星
int d[2 * NMMAX], cur[2 * NMMAX]; //d存储的是每次BFS得到的层数,cur存储的是当前已经遍历到的位置
bool vis[2 * NMMAX];
void AddEdge(int from, int to, double cap) {
edges.push_back(edge(from, to, cap, 0));
edges.push_back(edge(to, from, 0, 0));
int cm = edges.size();
G[from].push_back(cm - 2);
G[to].push_back(cm - 1);
}
//利用BFS进行路径的增广
bool BFS(int s,int t) {
memset(vis, 0, sizeof(vis));
queue<int> Q;
Q.push(s);
d[s] = 0;
vis[s] = 1;
while (!Q.empty()) {
int x = Q.front();
Q.pop();
for (int i = 0; i < G[x].size(); i++) {
edge& e = edges[G[x][i]];
if (!vis[e.to] && e.cap > e.flow) {
vis[e.to] = 1;
d[e.to] = d[x] + 1;
Q.push(e.to);
}
}
}
return vis[t];
}
//利用DFS进行参与网络的修正
double DFS(int x, double a ,int s,int t) {
if (x == t || a == 0) return a;
double flow = 0;
double f;
for (int& i = cur[x]; i < G[x].size(); i++) {
edge& e = edges[G[x][i]];
if (d[x] + 1 == d[e.to] && (f = DFS(e.to, min(a, e.cap - e.flow),s,t)) > 0) {
e.flow += f;
edges[G[x][i] ^ 1].flow -= f;
flow += f;
a -= f;
if (a == 0) break;
}
}
return flow;
}
double Maxflow(int s, int d) {
double flow = 0;
while (BFS(s,d)) {
memset(cur, 0, sizeof(cur));
flow += DFS(s, INF,s,d);
}
return flow;
}
int main() {
cin >> T;
while (T--) {
cin >> m >> n >> l;
int s = 0, d = m + n + 1;
//清空
for (int i = 1; i < d; i++) G[i].clear();
edges.clear();
for (int i = 1; i <= m; i++) {
double cur;
cin >> cur;
AddEdge(s, i, log(cur));
}
for (int i = m+1; i <= m+n; i++) {
double cur;
cin >> cur;
AddEdge(i, d, log(cur));
}
for (int i = 0; i < l; i++) {
int x, y;
cin >> x >> y;
AddEdge(x, y+m, INF);
}
//for (int i = 0; i < edges.size(); i++) cout << edges[i].from << " " << edges[i].to << " " << edges[i].cap << endl;
printf("%.4f\n", exp(Maxflow(s, d))); //注意这里不能使用.4g
}
}
%g %f %e
总结
网络流模型中较为重要的便是图的建立了,为此需要多多积攒题目经验。