0
点赞
收藏
分享

微信扫一扫

算法刷题记录(DAY 11)

佳简诚锄 2022-03-11 阅读 79

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

总结

网络流模型中较为重要的便是图的建立了,为此需要多多积攒题目经验。

举报

相关推荐

0 条评论