题意:设有这么一个m*n的矩阵,给定它的每一行的和及每一列的和,以及矩阵中一些元素的上下界限制条件。判断满足这些条件的矩阵是否存在,若不存在输出“IMPOSSIBLE”,若存在,则给出这个矩阵的一种符合要求的形式。
作为到处推荐的经典构图题,很有意思呢~利用容量有上下限的网络最大流思想,很容易想到把这些条件作为弧。得到模型如下:
1.把矩阵中m*n个元素和m+n个和作为节点,建立源点和汇点。
2.从源点向每行和引容量为行之和的弧,从每列和向汇点引容量为列之和的弧。
3.从每行的和分别向每个元素引一条容量为[0,inf]的弧,从每一个元素向该列的和引出条件如限制的弧。那么如果源点流出的流能全部到达汇点,说明有解。
4.再思考一步,第i行的和经过元素(i,j)流向第j列的和的显然路径是唯一确定的,所以我们可以把弧直接从第i行和的节点引向第j行列的节点,并不会丢失信息,这样可以把图简化到m+n+2个节点,然后用模型解题就可以了。
#include<cstdio>
#include<climits>
#include<cstring>
#define MAXV 300
const int inf=1<<31-1;
int g[MAXV][MAXV],high[MAXV][MAXV],low[MAXV][MAXV],f[MAXV][MAXV];//,c[MAXN][MAXN],f[MAXN][MAXN];
int m,n,x,y,s,t,V,pos,Case;
int vd[MAXV],d[MAXV],in[MAXV],out[MAXV];
inline int min(int a,int b){return a<b?a:b;}
inline int max(int a,int b){return a>b?a:b;}
int dfs(int u,int s,int t,int n,int flow)
{
int ret,v,tmp;
if(u == t)return flow; //找到增广路
ret = 0; //min_d:u在残量网络里相通的边的最小标号距离,初始不能变为maxint
for(v=1;v<=n;v++)
if(g[u][v] > 0 && d[u] == d[v] + 1) //如果是允许弧
{
tmp = dfs(v,s,t,n,min(flow-ret,g[u][v])); //如果找到,增广
g[u][v] -= tmp;
g[v][u] += tmp;
f[u][v]+=tmp;
f[v][u]-=tmp;
ret += tmp;
if(ret == flow)return ret;
}
if(d[s] >= n) return ret; //如果源点的标号距离大于n,即不存在增广路,这一步放在重标号之前
vd[d[u]]--;
if(vd[d[u]] == 0)d[s] = n; //如果该标号距离的顶点是唯一的,那么删除后图出现断层
d[u]++;
vd[d[u]]++;
return ret;
}
int main()
{
freopen("in","r",stdin);
freopen("out","w",stdout);
int i,j,a,b,w,flow,sum,ans,s,t,time,ss,tt;
char fu;
scanf("%d",&Case);
while(Case--){
memset(g,0,sizeof(g));
memset(high,0,sizeof(high));
memset(low,0,sizeof(low));
memset(in,0,sizeof(in));
memset(out,0,sizeof(out));
memset(f,0,sizeof(f));
memset(d,0,sizeof(d));
memset(vd,0,sizeof(vd));
pos=1;sum=ans=0;
scanf("%d %d\n",&m,&n);
s=m+n+1;t=s+1;ss=t+1;tt=t+2; //s,t为源汇点,ss和tt为附加源汇点
V=t+2;vd[0]=V;
for(i=1;i<=m;i++)for(j=1;j<=n;j++)
high[i][j+m]=inf; //保存上下界
for(i=1;i<=m;i++){
scanf("%d",&w);
high[s][i]=low[s][i]=w;
}
for(j=1;j<=n;j++){
scanf("%d",&w);
high[j+m][t]=low[j+m][t]=w;
}
for(i=1;i<=m;i++)
for(j=1;j<=n;j++){
g[i][m+j]=inf;
}
scanf("%d",&time);
while(time--){
scanf("%d %d %c %d",&a,&b,&fu,&w);
if(a!=0 && b!=0)switch(fu){
case '>' :
low[a][b+m]=max(low[a][b+m],w+1);
break;
case '=' :
high[a][b+m]=low[a][b+m]=w;
break;
case '<':
high[a][b+m]=min(high[a][b+m],w-1);
break;
}
else if(a==0 && b!=0)switch(fu){
case '>' :
for(i=1;i<=m;i++)low[i][b+m]=max(low[i][b+m],w+1);
break;
case '=' :
for(i=1;i<=m;i++)high[i][b+m]=low[i][b+m]=w;
break;
case '<':
for(i=1;i<=m;i++)high[i][b+m]=min(high[i][b+m],w-1);
break;
}
else if(a!=0 && b==0)switch(fu){
case '>' :
for(j=1;j<=n;j++)low[a][j+m]=max(low[a][j+m],w+1);
break;
case '=' :
for(j=1;j<=n;j++)high[a][j+m]=low[a][j+m]=w;
break;
case '<':
for(j=1;j<=n;j++)high[a][j+m]=min(high[a][j+m],w-1);
break;
}
else switch(fu){
case '>' :
for(i=1;i<=m;i++)for(j=1;j<=n;j++) low[i][j+m]=max(low[i][j+m],w+1);
break;
case '=' :
for(i=1;i<=m;i++)for(j=1;j<=n;j++) high[i][j+m]=low[i][j+m]=w;
break;
case '<':
for(i=1;i<=m;i++)for(j=1;j<=n;j++) high[i][j+m]=min(high[i][j+m],w-1);
break;
}
} //终于读进来了,然后来开心的yy吧= =
for(i=1;i<=V-2;i++)for(j=1;j<=V-2;j++){
g[i][j]=high[i][j]-low[i][j];
out[i]+=low[i][j]; //in表示流入该节点下界的和,out表示流出该节点下界的和
in[j]+=low[i][j];
sum+=low[i][j];
}
for(i=1;i<=V-2;i++){
g[ss][i]=in[i];
g[i][tt]=out[i];
}
g[t][s]=inf;
while(d[ss]<V){ //第一次求最大流,判断是否有解
flow=dfs(ss,ss,tt,V,inf);
ans+=flow;
}
if(ans!=sum){ //不存在满足上下限的可行流
printf("IMPOSSIBLE\n\n");
continue;
}
g[t][s]=g[s][t]=0; //删除tt和ss及必要弧
V-=2;
memset(d,0,sizeof(d));
memset(vd,0,sizeof(vd));
while(d[s]<V){ //第二次最大流
flow=dfs(s,s,t,V,inf);
ans+=flow;
}
for(i=1;i<=m;i++){
for(j=1;j<n;j++)
printf("%d ",f[i][j+m]+low[i][j+m]);
printf("%d\n",f[i][m+n]+low[i][j+m]);
}
printf("\n");
}
return 0;
}
此处题解受益良多:
http://hi.baidu.com/%8E%E1%D0%B3/blog/item/0bc9238072e254b16c811987.html 这篇也相当清晰:http://acm.hrbeu.edu.cn/forums/index.php?showtopic=2940
我用的是ISAP算法求最大流,代码貌似算是比较短的,读数太恶心,诶~
还有一个问题就是,此题在POJ上是Special Judge,不止一种答案。那么OJ是怎么判的呢?是用约束条件来检验,还是有办法可以求出每一种可行的情况呢?如果是后者,应该有很有意思的算法吧…知道的同学求解释lol……
多源多汇的网络流问题:
添加超级源点,到多个源点引容量为inf的弧;添加超级汇点,从多个汇点向超级汇点引容量为inf的弧。正确性显然。
容量有上下界限制的网络最大流问题:
首先是可行流的探讨:
1.把以[a,b]为上下界的弧拆成容量为a的必要弧和容量为b-a的非必要弧。
2.建立附加源点ss和附加汇点tt,使tt-ss的容量为inf,把从u-v的必要弧改为u-tt,tt-ss,ss-v,其中u-tt和ss-v的容量同为a。
3.为明确源和汇,去除弧tt-ss,新增弧t-s,容量为inf,s和t为原源汇点。至此附加网络建成。
4.计算ss-tt的最大流,如果从ss出发的sum(ai)的和全部能够到达tt,则说明有符合条件的可行流存在。由必要弧必须饱和,正确性显然。
证明存在性后我们来算最大流。
5.去除tt和ss,去除弧t-s,在原图上求原网络最大流,需要注意的是必要弧不可以退流,所以可以直接删去,计算结束再加回去即可。
容量有上下界限制的网络的最小流问题:
1.探讨可行流步骤与上面最大流的步骤1~4完全一致,第5改成以t为源点s为汇点,倒向求解最大流即为所求。
2.另有一种方法是ZZY大人06年国家集训队提出的算法ORZ:
建立新源汇点ds dt,对于任意点a:
连接ds->a,容量等于a为终点的所有的边的下界和;
连接a->dt,容量等于a为起点的所有的边的下界和;
这样求一次ds->dt的最大流
然后原来t->s加流量无穷大的边,再求多次ds->dt的最大流
判断ds出发所有的边是否都流满了,如果都流满了才有解,否则无解。
那么t->s上的流量就是最小流。
ym大神的传送门:http://hi.baidu.com/dragon_eric123/blog/item/00cd5ac981a9b71f7f3e6f24.html
其实吧这类有上下界限制的网络流问题都可以用二分查找解决,也容易理解。
传送至XDU上古神牛wm处膜拜:http://www.cppblog.com/RyanWang/archive/2009/08/18/93672.html
ISAP+GAP+邻接矩阵 beta1.1
我又把模板给改了……
int dfs(int u,int s,int t,int n,int flow)
//当前点u,源点s,汇点t,共n个点,当前流flow
{
int ret,v,tmp;
if(u == t)return flow; //找到增广路
ret = 0; //min_d:u在残量网络里相通的边的最小标号距离,初始不能变为maxint
for(v=1;v<=n;v++)
if(g[u][v] > 0 && d[u] == d[v] + 1) //如果是允许弧
{
tmp = dfs(v,s,t,n,min(flow-ret,g[u][v])); //如果找到,增广
g[u][v] -= tmp;
g[v][u] += tmp;
f[u][v]+=tmp;
f[v][u]-=tmp;
ret += tmp;
if(ret == flow)return ret;
}
if(d[s] >= n) return ret; //如果源点的标号距离大于n,即不存在增广路,这一步放在重标号之前
vd[d[u]]--;
if(vd[d[u]] == 0)d[s] = n; //如果该标号距离的顶点是唯一的,那么删除后图出现断层
d[u]++;
vd[d[u]]++;
return ret;
}
调用形式:
while(d[s]<V){ //第二次最大流
flow=dfs(s,s,t,V,inf);
ans+=flow;
}
话唠:矮油,这么快7月底了啊~你还差的远呢……
这个故事告诉我们果然什么数据结构的模板都要有一个啊~
图论题终于开始没有图了,点不是点,边也不一定是边,关键是要熟悉模型性质,弄清本质,套错了多囧啊= =
大象无形,多有意思的事情呢~