HDU4888 Redraw Beautiful Drawings(最大流唯一性判定:残量网络删边判环)
题目
Source
http://acm.hdu.edu.cn/showproblem.php?pid=4888
Description
Alice and Bob are playing together. Alice is crazy about art and she has visited many museums around the world. She has a good memory and she can remember all drawings she has seen.
Today Alice designs a game using these drawings in her memory. First, she matches K+1 colors appears in the picture to K+1 different integers(from 0 to K). After that, she slices the drawing into grids and there are N rows and M columns. Each grid has an integer on it(from 0 to K) representing the color on the corresponding position in the original drawing. Alice wants to share the wonderful drawings with Bob and she tells Bob the size of the drawing, the number of different colors, and the sum of integers on each row and each column. Bob has to redraw the drawing with Alice's information. Unfortunately, somtimes, the information Alice offers is wrong because of Alice's poor math. And sometimes, Bob can work out multiple different drawings using the information Alice provides. Bob gets confused and he needs your help. You have to tell Bob if Alice's information is right and if her information is right you should also tell Bob whether he can get a unique drawing.
Input
The input contains mutiple testcases.
For each testcase, the first line contains three integers N(1 ≤ N ≤ 400) , M(1 ≤ M ≤ 400) and K(1 ≤ K ≤ 40).
N integers are given in the second line representing the sum of N rows.
M integers are given in the third line representing the sum of M columns.
The input is terminated by EOF.
Output
For each testcase, if there is no solution for Bob, output "Impossible" in one line(without the quotation mark); if there is only one solution for Bob, output "Unique" in one line(without the quotation mark) and output an N * M matrix in the following N lines representing Bob's unique solution; if there are many ways for Bob to redraw the drawing, output "Not Unique" in one line(without the quotation mark).
Sample Input
2 2 4
4 2
4 2
4 2 2
2 2 5 0
5 4
1 4 3
9
1 2 3 3
Sample Output
Not Unique
Impossible
Unique
1 2 3 3
分析
题目大概说有一个矩阵,已知各个单元的值在0到k之间以及各行各列的和,要求还原该矩阵,如果唯一的话就输出解。
经典的构图了吧,POJ2396。
- 把行、列看成点,各单元看成边
而问题关键在于判定最大流唯一性。
这个结论是,残量网络不存在环是最大流唯一的充要条件。找到环绕着一圈调整一下环上边的流量,比如流量-1(同时对应的反向边或者正向边流量+1),这样又会是一个满足平衡条件的最大流。
在残量网络中找环,不能用拓扑排序。因为正向边和反向边应该要看成一体,即如果统计了正向边贡献的出入度,那么反向边的出入度就不需要统计了,反之亦然,而我们没法确定各边是选正向还是反向。
那么环就用搜索找了:
bool dfs(int u,int fa){ vis[u]=1; for(int i=head[u]; i!=-1; i=edge[i].next){ int v=edge[i].v; if(v==fa || edge[i].cap==edge[i].flow) continue; if(vis[v]) return 1; if(dfs(v,u)) return 1; } vis[u]=0; return 0; }
不过这个怎么看都是指数级时间复杂度。
我在这个博客看到了删边的做法。确实,回溯后说明往当前这条边走是找不到环的,以后不必要再往这条边走,这样每条边最多都只走一次就保证了时间复杂度为多项式级别的了。
不过我看不懂那个博客的删边方式,我就比较low地标记删除的边,遇到被标记的就跳过。。不过这么做HDU4975是TLE的;于是我就自己想了想怎么真正地删边,然后实现了下,还是TLE,这不是线性时间复杂度了吗?。。好吧那题我选择放弃。
另外还有一个可以优化的是枚举环的起点只要枚举容量网络中行对应的点,我大概想了下是因为这题建图方式构成了二分图,那样从列对应的点出发找到环一定是会经过行对应的点的,于是从行对应的点出发也能形成这个环。
代码
#include<cstdio> #include<cstring> #include<queue> #include<algorithm> using namespace std; #define INF (1<<30) #define MAXN 888 #define MAXM 444*888 struct Edge{ int v,cap,flow,next; }edge[MAXM]; int vs,vt,NE,NV; int head[MAXN]; void addEdge(int u,int v,int cap){ edge[NE].v=v; edge[NE].cap=cap; edge[NE].flow=0; edge[NE].next=head[u]; head[u]=NE++; edge[NE].v=u; edge[NE].cap=0; edge[NE].flow=0; edge[NE].next=head[v]; head[v]=NE++; } int level[MAXN]; int gap[MAXN]; void bfs(){ memset(level,-1,sizeof(level)); memset(gap,0,sizeof(gap)); level[vt]=0; gap[level[vt]]++; queue<int> que; que.push(vt); while(!que.empty()){ int u=que.front(); que.pop(); for(int i=head[u]; i!=-1; i=edge[i].next){ int v=edge[i].v; if(level[v]!=-1) continue; level[v]=level[u]+1; gap[level[v]]++; que.push(v); } } } int pre[MAXN]; int cur[MAXN]; int ISAP(){ bfs(); memset(pre,-1,sizeof(pre)); memcpy(cur,head,sizeof(head)); int u=pre[vs]=vs,flow=0,aug=INF; gap[0]=NV; while(level[vs]<NV){ bool flag=false; for(int &i=cur[u]; i!=-1; i=edge[i].next){ int v=edge[i].v; if(edge[i].cap!=edge[i].flow && level[u]==level[v]+1){ flag=true; pre[v]=u; u=v; //aug=(aug==-1?edge[i].cap:min(aug,edge[i].cap)); aug=min(aug,edge[i].cap-edge[i].flow); if(v==vt){ flow+=aug; for(u=pre[v]; v!=vs; v=u,u=pre[u]){ edge[cur[u]].flow+=aug; edge[cur[u]^1].flow-=aug; } //aug=-1; aug=INF; } break; } } if(flag) continue; int minlevel=NV; for(int i=head[u]; i!=-1; i=edge[i].next){ int v=edge[i].v; if(edge[i].cap!=edge[i].flow && level[v]<minlevel){ minlevel=level[v]; cur[u]=i; } } if(--gap[level[u]]==0) break; level[u]=minlevel+1; gap[level[u]]++; u=pre[u]; } return flow; } bool vis[MAXN]; bool dfs(int u,int fa){ vis[u]=1; for(int i=head[u],pre=i; i!=-1; ){ int v=edge[i].v; if(v==fa || edge[i].cap==edge[i].flow){ pre=i; i=edge[i].next; continue; } if(vis[v]) return 1; if(dfs(v,u)) return 1; if(i==head[u]){ i=edge[i].next; head[u]=i; }else{ i=edge[i].next; edge[pre].next=i; } } vis[u]=0; return 0; } int ans[444][444]; int main(){ int n,m,k,a; while(~scanf("%d%d%d",&n,&m,&k)){ vs=0; vt=n+m+1; NV=vt+1; NE=0; memset(head,-1,sizeof(head)); int row=0,col=0; for(int i=1; i<=n; ++i){ scanf("%d",&a); addEdge(vs,i,a); row+=a; } for(int i=1; i<=m; ++i){ scanf("%d",&a); addEdge(i+n,vt,a); col+=a; } if(row!=col){ puts("Impossible"); continue; } for(int i=1; i<=n; ++i){ for(int j=1; j<=m; ++j){ addEdge(i,j+n,k); } } if(ISAP()!=row){ puts("Impossible"); continue; } for(int u=1; u<=n; ++u){ for(int i=head[u]; i!=-1; i=edge[i].next){ if(i&1) continue; int v=edge[i].v-n; ans[u][v]=edge[i].flow; } } bool flag=1; memset(vis,0,sizeof(vis)); for(int i=1; i<=n; ++i){ if(dfs(i,i)){ flag=0; break; } } if(!flag){ puts("Not Unique"); continue; } puts("Unique"); for(int i=1; i<=n; ++i){ for(int j=1; j<=m; ++j){ if(j!=1) putchar(' '); printf("%d",ans[i][j]); } putchar('\n'); } } return 0; }