這道題的建圖比較有講究,最大的限制條件就是特定的某行某列都選完元素之後這行這列對應的元素必須從備選元素中拿出。
否則就會出現重複,結果不可能是最小的。還有就是選中的元素的數目不是確定的
1 1 100
100 100 1
100 100 1
比如上面這個圖,因爲第1行第2個元素,和第2行第3個元素完全可以取代第2行第2列元素的覆蓋作用而且得到的和也更小。
爲了克服上面說的兩個難題
採用的建圖方式是一行對應兩個個節點,一列對應兩個節點。
建立一個超級源,到源點的容量限制是m*n費用是0。理由是可以保證每對行列之間都有流。
之後從源點向前m個行點連接1的容量,而費用是負無窮。我們的目標就是把這些流都給灌滿。
但是由於還要考慮列點的情況,所以還要再準備m個行點。這些行點起的是輔助作用,在上面的行點都
灌滿了,底下還沒灌滿的情況下幫忙灌滿底下列點的。
底下列點也同樣建立n個容量爲1費用爲負無窮的列點。只要上面那些負無窮行點和這些負無窮列點對應的1的流都灌滿了,
也就是結果找到了。爲了防止下面滿了上面沒滿,底下也要準備n個容量正無窮費用爲0的輔助點。
行點和列點之間就按對應點的數值大小連上容量就可以了。
之後爲了所在行和列都被選中後的點不出來搗亂,還要在源點和匯點之間加一個流量正無窮費用爲0的邊。
因爲行和列都被選中後在選這個點的話因爲肯定要走源點到行點費用爲0,列點到匯點費用爲0的邊。
元素還是正的所以必定費用爲正,肯定鬥不過費用爲0的邊。
得到結果後因爲多加了(m+n)個負無窮,減下去就可以了。
此外需要注意的是選流的時候有可能出現反悔的情況,所以要給行點和列點之間準備一條費用爲負方向相反的邊。
#include <iostream>
using namespace std;
struct Node
{
int cap, flow, cost;
};
Node edge[1000][1000];
int sum;
int size = 7;
int s = 0, t = 6, m, n;
int pre[1000];
int q[2000];
int vis[1000], dis[1000];
int a[1000][1000];
void get(int i, int j, int cost, int cap)
{
edge[i][j].cap = cap;
edge[i][j].cost = cost;
edge[j][i].cost = cost * -1;
}
void init()
{
int i, j;
size =2 * m + 2 * n + 3;
for(i = 0; i < size; i++)
{
for(j = 0; j < size; j++)
{
edge[i][j].flow = 0;
edge[i][j].cap = 0;
edge[i][j].cost = 100000;
}
}
s = 2 * m + 2 * n + 1;
t = 2 * m + 2 * n + 2;
get(s, 0, 0, m * n);
get(0, t, 0, m * n);
for(i = 1; i <= m; i++)
get(0, i, -100000, 1);
for(i = 1; i <= m; i++)
get(0, m + i, 0, n);
for(i = 1; i <= m; i++)
{
for(j = 1; j <= n; j++)
{
get(i, 2 * m + j, a[i][j], 1);
get(i, 2 * m + n + j, a[i][j], 1);
get(i + m, 2 * m + j, a[i][j], 1);
get(i + m, 2 * m + n + j, a[i][j], 1);
}
}
for(i = 1; i <= n; i++)
get(2 * m + i, t, -100000, 1);
for(i = 1; i <= n; i++)
get(2 * m + n + i, t, 0, m);
}
int spfa()
{
int i, temp;
for(i = 0; i < size; i++)
{
vis[i] = -1;
dis[i] = 100000;
}
int front = 0, tail= 1;
vis[s] = 1; dis[s] = 0;
q[front] = s;
while(front < tail)
{
temp = q[front];
vis[temp] = -1;
front++;
for(i = 0; i < size; i++)
{
int res = edge[temp][i].cap - edge[temp][i].flow;
if(res > 0 && dis[i] > dis[temp] + edge[temp][i].cost)
{
dis[i] = dis[temp] + edge[temp][i].cost;
pre[i] =temp;
if(vis[i] == -1)
{
vis[i] = 1;
q[tail++] = i;
}
}
}
}
return dis[t];
}
int MCMF()
{
int temp, min, flow, sum = 0, cost, val;
while(spfa() < 8000)
{
temp = t;
flow = 100000; cost = 0;
while(temp != s)
{
val = edge[pre[temp]][temp].cap - edge[pre[temp]][temp].flow;
cost += edge[pre[temp]][temp].cost;
if(val < flow)
flow = val;
temp = pre[temp];
}
temp = t;
sum += flow * cost;
while(temp != s)
{
edge[pre[temp]][temp].flow += flow;
edge[temp][pre[temp]].flow -= flow;
temp = pre[temp];
}
}
return sum;
}
int main()
{
int size;
cin >> size;
while(size--)
{
cin >> m >> n;
for(int j = 1; j <= n; j++)
{
for(int i = 1; i <= m; i++)
{
cin >> a[i][j];
}
}
init();
cout << MCMF() + (m + n) * 100000<< endl;
}
return 0;
}
題目3 :格格取數
時間限制:2000ms
單點時限:1000ms
內存限制:256MB
描述
給你一個m x n (1 <=m, n <= 100)的矩陣A (0<=aij<=10000),要求在矩陣中選擇一些數,要求每一行,每一列都至少選到了一個數,使得選出的數的和儘量的小。
輸入
多組測試數據。首先是數據組數T
對於每組測試數據,第1行是兩個正整數m, n,分別表示矩陣的行數和列數。
接下來的m行,每行n個整數,之間用一個空格分隔,表示矩陣A的元素。
輸出
每組數據輸出一行,表示選出的數的和的最小值。
數據範圍
小數據:1 <= m, n<= 5
大數據:1 <= m, n<= 100
樣例輸入
2
3 3
1 2 3
3 1 2
2 3 1
5 5
1 2 3 4 5
5 1 2 3 4
4 5 1 2 3
3 4 5 1 2
2 3 4 5 1
樣例輸出
Case 1: 3
Case 2: 5