Going Home(poj 2195)
原题链接
题目类型:带权值的二分图的完美匹配问题
使用KM算法。由于KM算法是用于求最大权值的问题,因此我们需要将权值取为负数。
#include<iostream>
#include<vector>
using namespace std;
#define NMMAX 110
#define INF 0x3f3f3f3f
//KM算法所需要的变量
int map[NMMAX][NMMAX];
int Lx[NMMAX], Ly[NMMAX];//顶标
int X[NMMAX], Y[NMMAX];//代表是否已经访问过
int G[NMMAX];//代表的是当前已经为Y匹配到了X中的哪一个元素
int N,C;
vector<pair<int, int> > man;
vector<pair<int, int> > house;
void update() {
int inc = INF;
//为了给冲突的X集合中的点添加边,应该从Y中非冲突的点中找
//需要确定最小的步长,以达到最后的收益最大的情况
for (int i = 1; i <= N; i++) {
if (X[i]) {
for (int j = 1; j <= N; j++) {
if (!Y[j]) inc = min(inc, Lx[i] + Ly[j] - map[i][j]);
}
}
}
//为了是本来就存在的边依然存在,X中的点减去inc,Y中的点加上inc
for (int i = 1; i <= N; i++) {
if (X[i]) Lx[i] -= inc;
}
for (int j = 1; j <= N; j++) {
if (Y[j]) Ly[j] += inc;
}
}
int find(int x) {
X[x] = 1;
//遍历Y集中的点
for (int j = 1; j <= N; j++) {
if (Lx[x] + Ly[j] == map[x][j] && !Y[j]) {
Y[j] = 1;
if (!G[j] || find(G[j])) {
G[j] = x;
return 1;
}
}
}
return 0;
}
int KM() {
//初始化顶标和匹配数组
int res = 0;//代表的是匹配数
memset(G, 0, sizeof(G));
for (int i = 1; i <= N; i++) {
Lx[i] = -INF;
for (int j = 1; j <= N; j++) {
Ly[j] = 0;
Lx[i] = max(Lx[i], map[i][j]);
}
}
for(int i = 1; i <= N; i++){//为X中的元素寻找匹配
while (1) {
//注意:这里没进行一次查找都要清空!!!
memset(X, 0, sizeof(X));
memset(Y, 0, sizeof(Y));
if (find(i)) {
res++;
break;
}
//没有给i找到匹配,修改顶标,并更新
update();
}
}
if (res != N) cout << "NO MATCH!" << endl;
else {
int ret = 0;
for (int i = 1; i <= N; i++) ret += (Lx[i]+Ly[i]);
return ret;
}
return 0;
}
int main() {
while (1) {
int n, m;
cin >> n >> m;
if (!n && !m) break;
man.clear();
house.clear();
for (int i = 0; i < n; i++) {
getchar();
for (int j = 0; j < m; j++) {
char c = getchar();
if (c == 'm') man.push_back(make_pair(i, j));
if (c == 'H') house.push_back(make_pair(i, j));
}
}
N = man.size();
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
map[i + 1][j + 1] = -abs(man[i].first - house[j].first) - abs(man[i].second - house[j].second);
//cout << map[i + 1][j + 1];
}
//cout << endl;
}
cout << -KM() << endl;
}
}
debug过程:
1.map[i + 1][j + 1] = -abs(man[i].first - house[j].first) - abs(man[i].second - house[j].second);中的j误写成了i导致了结果的错误
2.KM算法中的标记位在为每一个X集合中的元素的每一次匹配寻找过程都必须重置,否则会导致TL问题
Power Network(poj 2459)
解题思路:设置一个源点,其连接到各个power station,边值为每个供电站的供电最大值;设置一个汇点,连接各个consumer,边值为每个消费节点的消耗最大值。而其余各个点之间的连线的流量最大值直接从题目中获得。
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
#define maxn 150 // count of Node
#define INF 0x3f3f3f3f
int n, np, nc, m, s, t,cm;//n代表的是点数,m代表的是总的边数,cm代表的是当前的边数
vector<int> G[maxn]; //G是链式前向星的结构
int d[maxn], cur[maxn]; //d存储的是每次BFS得到的层数,cur存储的是当前已经遍历到的位置
bool vis[maxn];
struct Edge {
int from, to, cap, flow;
Edge(int u, int v, int c, int f) : from(u), to(v), cap(c), flow(f) {}
};
vector<Edge> edges;
void init(int n) {
for (int i = 0; i < n; i++) G[i].clear();
edges.clear();
}
void AddEdge(int from, int to, int cap) {
edges.push_back(Edge(from, to, cap, 0));
edges.push_back(Edge(to, from, 0, 0));
cm = edges.size();
G[from].push_back(cm - 2);
G[to].push_back(cm - 1);
}
bool BFS() {
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];
}
int DFS(int x, int a) {
if (x == t || a == 0) return a;
int flow = 0;
int 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))) > 0) {
e.flow += f;
edges[G[x][i] ^ 1].flow -= f;
flow += f;
a -= f;
if (a == 0) break;
}
}
return flow;
}
int Maxflow(int s, int t) {
int flow = 0;
while (BFS()) {
memset(cur, 0, sizeof(cur));
flow += DFS(s, INF);
}
return flow;
}
int main() {
while (cin >> n >> np >> nc >> m) {
//定义源点和汇点
s = n;
t = n + 1;
n = n + 2;
init(n);
for (int i = 0; i < m; i++) {
int cs, ct, cf;
while (getchar() != '(');
//cin >> cs >> ct >> cf; 注意不能这样来读
scanf_s("%d,%d)%d", &cs, &ct, &cf);
AddEdge(cs, ct, cf);
}
for (int i = 0; i < np; i++) {
int ct, cf;
// cin >> ct >> cf;
while (getchar() != '(');
scanf_s("%d)%d",&ct, &cf);
AddEdge(s, ct, cf);
m++;
}
for (int i = 0; i < nc; i++) {
int cs, cf;
// cin >> cs >> cf;
while (getchar() != '(');
scanf_s("%d)%d", &cs, &cf);//这里的格式化字符串使用错误
AddEdge(cs, t, cf);
m++;
}
cout << Maxflow(s,t) << endl;
}
}
debug过程:
1.由于题目给出的格式较为复杂,因此进行关于边的读入的时候不能过于简单化,对于 (a,b)c 直接使用cin>>a>>b>>c是错误的,必须首先使用getchar将(以前的字符全部读取,然后使用scanf(“%d,%d)%d",&a,&b&c)才能正确地进行读入。
tip: 对于没有终结符的输入,可以使用while(cin>>a),当到达文件尾的时候,会自动停止。
总结
需要做一个总的关于图论的内容。