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啦)