1月14號的時候看Fancy學姐的博客學網絡流,寫的很好,很容易懂。然後就開始學有源匯上下界可行流,方法很簡單,就添一條t到s上下界爲0, INT_MAX的邊然後按無源匯上下界可行流的做就行了【但是具體爲什麼這樣可以我還不是很懂。。。。】
於是開始做題,就這道POJ2396。
我簡單說下題意:給你一個矩陣,每行的和,每列的和,和一些限制條件,讓你輸出可行的矩陣。
Fancy說的方法是對於每行和每列分別建一個點,ss 與每行的點連 [a[i],a[i]][a[i],a[i]] 的邊,每列的點與 tt連 [b[i],b[i]][b[i],b[i]] 的邊,中間連的邊爲點(i,j)(i,j) 的限制
我嘗試着胡寫但是不可行。網上其他的題解也大都是這個思路,而且代碼還都是數組的,我寫鄰接表啊根本沒法抄qnq
這時候我看了一下午已經到了晚上,然後Menci看了,給我說了一下具體的解法,是這樣的:我們把一行當做一個點,一列當做一個點,建一個源點s往每個行點上連邊,上下界都爲行和;每個列點往匯點t上連邊,上下界爲列和。
我們想象再爲矩陣上的每一個數建一個點,約束【比如說2 3 > 2 第二行第三列上的那一個店要大於2】就是那一個行點【2】,向那個點連邊,那個點再向有約束的另一個點連邊,這個點再連向列點(如果是一整行或者一整列的約束把它拆分成每個點來做)。但是這樣的話就相當於從行點直接連向列點,因爲流量平衡,所以我們就可以省掉每一個數的點,只需要n + m個點就可以了。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <climits>
using namespace std;
const int MAXN = 400 + 5;
const int MAXM = (MAXN * (MAXN - 1)) / 2;
const int MAXr = 200 + 5, MAXc = 20 + 5;
int minn[MAXr][MAXc], maxx[MAXr][MAXc];
int a[MAXr], b[MAXc];
int n, m;
struct Node {
struct Edge *lastE, *curE;
int level, extraIn;
} N[MAXN];
struct Edge {
Node *from, *to;
int flow, cap, low;
Edge *next, *revE;
Edge(Node *from, Node *to, int cap) : from(from), to(to), flow(0), cap(cap), next(from->lastE) {}
} *E[MAXr][MAXc];
struct Dinic {
bool makeLevelGraph(Node *s, Node *t, int n)
{
for (int i = 0; i < n; i++)
{
N[i].level = 0;
N[i].curE = N[i].lastE;
}
queue<Node *> q;
q.push(s);
s->level = 1;
while (!q.empty())
{
Node *v = q.front();
q.pop();
for (Edge *e = v->lastE; e; e = e->next)
{
if (e->flow < e->cap && !e->to->level)
{
e->to->level = v->level + 1;
//printf("%d !\n", e->to->level);
if (e->to == t)
{
return true;
}
else
{
q.push(e->to);
}
}
}
}
return false;
}
int findPath(Node *s, Node *t, int limit = INT_MAX)
{
if (s == t) return limit;
for (Edge *&e = s->curE; e; e = e->next)
{
if (e->flow < e->cap && e->to->level == s->level + 1)
{
int flow = findPath(e->to, t, min(limit, e->cap - e->flow));
if (flow)
{
e->flow += flow;
e->revE->flow -= flow;
return flow;
}
}
}
return 0;
}
int operator()(int s, int t, int n)
{
int res = 0;
while (makeLevelGraph(&N[s], &N[t], n))
{
int flow;
while ((flow = findPath(&N[s], &N[t])) > 0)
{
res += flow;
//printf("%d $ %d ", flow, res);
}
//printf("\n");
}
return res;
}
} dinic;
inline Edge *addEdge(int u, int v, int cap)
{
N[u].lastE = new Edge(&N[u], &N[v], cap);
N[v].lastE = new Edge(&N[v], &N[u], 0);
N[u].lastE->revE = N[v].lastE;
N[v].lastE->revE = N[u].lastE;
return N[u].lastE;
}
inline Edge *addEdge(int u, int v, int low, int high)
{
int cap = high - low;
Edge *e = addEdge(u, v, cap);
e->low = low;
N[u].extraIn -= low;
N[v].extraIn += low;
return e;
}
inline void change(int r, int c, char fu, int d)
{
if (fu == '>')
{
minn[r][c] = max(minn[r][c], d + 1);
}
else if (fu == '<')
{
maxx[r][c] = min(maxx[r][c], d - 1);
}
else if (fu == '=')
{
minn[r][c] = max(minn[r][c], d);
maxx[r][c] = min(maxx[r][c], d);
}
// if (minn[r][c] > maxx[r][c])
// {
// return false;
// }
//
// return true;
}
inline bool flow(int s, int t, int n)
{
int S = 0, T = n + 1;
addEdge(t, s, INT_MAX);
int sum = 0;
for (int i = 1; i <= n; i++)
{
if (N[i].extraIn > 0)
{
addEdge(S, i, N[i].extraIn);
sum += N[i].extraIn;
}
else if (N[i].extraIn < 0)
{
addEdge(i, T, -N[i].extraIn);
}
}
return dinic(S, T, n + 2) == sum;
// int flow = dinic(S, T, n + 2);
// if (flow < sum) return false;
// return true;
}
inline bool solve()
{
int s = n + m + 1, t = n + m + 2;
//addEdge(t, s, 0, INT_MAX);
for (int i = 1; i <= n; i++)
{
addEdge(s, i, a[i], a[i]);
}
for (int i = 1; i <= m; i++)
{
addEdge(i + n, t, b[i], b[i]);
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (minn[i][j] > maxx[i][j]) return false;
E[i][j] = addEdge(i, j + n, minn[i][j], maxx[i][j]);
//printf("%d~ %d ", minn[i][j], maxx[i][j]);
}
//printf("\n");
}
//printf("\n");
return flow(s, t, n + m + 2);
}
int main()
{
int num;
scanf("%d", &num);
while (num--)
{
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
minn[i][j] = 0;
maxx[i][j] = INT_MAX;
}
}
// memset(a, 0, sizeof(a));
// memset(b, 0, sizeof(b));
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
}
for (int i = 1; i <= m; i++)
{
scanf("%d", &b[i]);
}
int k;
scanf("%d", &k);
//bool flag = true;
//int cnt = n
while (k--)
{
//scanf("%d %d", &n, &m);
int r, c, d;
char fu;
scanf("%d %d %c %d", &r, &c, &fu, &d);
//先存起來,最後再加
//c = c + n;
if (r == 0 && c != 0)
{
for (int i = 1; i <= n; i++)
{
change(i, c, fu, d);
// if (change(i, c, fu, d) == false)
// {
// flag = false;
// }
}
}
else if (c == 0 && r != 0)
{
for (int i = 1; i <= m; i++)
{
change(r, i, fu, d);
// if (change(r, i, fu, d) == false)
// {
// flag = false;
// }
}
}
else if (r == 0 && c == 0)
{
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
change(i, j, fu, d);
// if (change(i, j, fu, d) == false)
// {
// flag = false;
// }
}
}
}
else
{
change(r, c, fu, d);
//if (change(r, c, fu, d) == false)
// {
// flag = false;
// }
}
}
// if (flag == false)
// {
// printf("IMPOSSIBLE\n");
// if (num != 0)
// {
// printf("\n");
// }
// continue;
// }
bool flag = solve();
if (flag)
{
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
printf("%d ", E[i][j]->flow + E[i][j]->low);
}
printf("\n");
}
if (num != 0)
{
printf("\n");
}
}
else
{
printf("IMPOSSIBLE\n");
if (num != 0)
{
printf("\n");
}
}
for (int i = 0; i <= n + m + 4; i++)
{
for (Edge *&e = N[i].lastE, *next; e; next = e->next, delete e, e = next);
N[i].level = N[i].extraIn = 0;
}
// int s = n + 2, t = n + 3;
// for (int i = )
}
return 0;
}
我是抄Menci的板寫的鄰接鏈表實現的Dinic
註釋掉的有一些是中間的調試信息
大的框架14號晚上就寫完了,然後樣例都過不了(微笑)16號又調了一晚自習,今天整整一上午,都有點噁心qnq期間略過對着Menci的程序肉眼Debug不提,主要我出的錯有一下幾個:
1、約束那裏是先用一個二維數組把上下界存起來,方便修改,>和<的時候要minn[i][j] = max(minn[i][j], d + 1)'
同樣maxx[i][j] = min(maxx[i][j], d - 1)
因爲我們後來用的時候是大於d的整數,也就是等於d + 1;
2、一開始是給maxx數組賦初值INT_MAX,給minn數組賦初值0,而不是INT_MIN, 因爲流量的下界是0,而不能是負的;
3、這裏點的編號是1~n + m的,但邊是用二維數組存的(這裏只存點之間的邊就行,其他的邊可以不用存在E這個指針數組裏),所以循環的時候一定要注意是1~n,1~m還是1~n,n + 1~n + m【我檢查了辣麼多遍函數結果主函數寫錯了。。。。】
4、需要的邊用一個二維指針數組存起來比較方便,我覺得用結構體不太好實現(可能是我辣雞(:з」∠))
這道題算法其實還比較好想(雖然大概我也想不出來),但是實現的時候有點噁心(還是我太弱。。。dalao很快就A啦)