hdu4975 A simple Gaussian elimination problem.(正确解法 最大流+删边判环)(Updated 2014-10-16)

这题标程是错的,网上很多题解也是错的。

http://acm.hdu.edu.cn/showproblem.php?pid=4975

2014 Multi-University Training Contest 10

A simple Gaussian elimination problem.

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 458    Accepted Submission(s): 153


Problem Description
Dragon is studying math. One day, he drew a table with several rows and columns, randomly wrote numbers on each elements of the table. Then he counted the sum of each row and column. Since he thought the map will be useless after he got the sums, he destroyed the table after that.

However Dragon's mom came back and found what he had done. She would give dragon a feast if Dragon could reconstruct the table, otherwise keep Dragon hungry. Dragon is so young and so simple so that the original numbers in the table are one-digit number (e.g. 0-9).

Could you help Dragon to do that?
 

Input
The first line of input contains only one integer, T(<=30), the number of test cases. Following T blocks, each block describes one test case.

There are three lines for each block. The first line contains two integers N(<=500) and M(<=500), showing the number of rows and columns.

The second line contains N integer show the sum of each row.

The third line contains M integer show the sum of each column.
 

Output
Each output should occupy one line. Each line should start with "Case #i: ", with i implying the case number. For each case, if we cannot get the original table, just output: "So naive!", else if we can reconstruct the table by more than one ways, you should output one line contains only: "So young!", otherwise (only one way to reconstruct the table) you should output: "So simple!".
 

Sample Input
3 1 1 5 5 2 2 0 10 0 10 2 2 2 2 2 2
 

Sample Output
Case #1: So simple! Case #2: So naive! Case #3: So young!
 

Source
 

Recommend
hujie   |   We have carefully selected several similar problems for you:  4980 4979 4978 4977 4975

题意:有n行m列矩阵,给出各行的和、各列的和,矩阵元素需要为0~9,判断无解、唯一解、多解。

题解:网络流+判环。

和hdu4888几乎一模一样,hdu4888题解链接:http://www.cnblogs.com/yuiffy/p/3891639.html

这题的做法就是hdu4888的做法,点上面的链接怒看如何建图,我下面说这题的不同之处。网上很多题解都是错的,标程也是错的。

这题标程有错,但没有反例的数据,所以数据还是对的。但标程使用的错误方法使它运行时间巨短,导致这题时限开得少,直接拿超碉优化的网络流+简单dfs判环也是过不了的。

正确做法应该是用更好的判环方法,我想到的是 dfs判环回溯时删边(不是删点),代码如下:

 1 bool dfs(const int &x,const int &prex) {///深搜判环
 2     int biu=-1;
 3     walked[x]=true;
 4     for (int i=head[x]; i!=-1; i=e[i].next) {
 5         if(e[i].v==prex){
 6             biu=i;
 7             continue;
 8         }
 9         if (e[i].cap>0) {
10             if(walked[e[i].v]) return true;
11             if(dfs(e[i].v,x)) return true;
12         }
13         if(biu==-1) head[x]=e[i].next;///删边,因为这条边为0或者走了这条边却没发现环
14         else e[biu].next=e[i].next;
15         biu=i;
16     }
17     walked[x]=false;
18     return false;
19 }

 

可以想到,如果选择一条边递归,然后从递归里出来了,说明没找到环,那么走这条边肯定找不到环,以后也不用走了,可以把这条边删了。或者这条边流量为0,也可以删了。

这样的话每条边最多只进一次,O(m)。

其实4888也可以用这种方法,不过4888还要输出结果,所以先记录结果再删。

 

然后我说一下网上常见的错误解法,因为没有反例数据,这种错误解法可以ac。

错误的dfs判环:

 1 bool walked[maxn];
 2 bool dfs(const int &x,const int &preE) {
 3     int pp=preE^1;
 4     for (int &i=head[x]; i!=-1; i=e[i].next) {
 5         if(i==(pp))continue;
 6         if (e[i].cap>0) {
 7             if(walked[e[i].v]) return true;
 8             walked[e[i].v]=true;
 9             if(dfs(e[i].v,i)) return true;
10             walked[e[i].v]=false;
11         }
12     }
13     return false;
14 }

网上很多地方都说这个int &i是个优化,加了这个优化就能过。其实这不是优化,这是乱搞,碰巧没有反例数据。

int &i=head[x]的意思是把i当成head[x]的引用,也就是i其实就是head[x]。

这样,i=e[i].next相当于head[x] = e[head[x]].next,是会改变head[x]的,也就是把上一条出边给扔了!这和我上面说的删边不一样,我上面的是只删递归过的边,而这个会把不能走的边也删掉。这题里不能走的边就是直接回头的那条边。

这样会发生什么情况呢?我来画个图:

1和3、2和3、2和4之间有双向边,而1和4之间只有单向边。当深搜1->3->2->4时,会把4的出边全扔了。本来我们应该找到的环是1->4->2->3->1,可是4的出边已经没了,找不到这个环。

由于大家建图的方法、网络流的方法、dfs的顺序不一致,所以这题不好找到对所有代码都成立的反例输入。如果谁发现好的样例的话希望能写出来,怒艹错误解法。

这个错解有人说是比赛时试出来的;还有人比赛时试验只在m,n<=200时判多解,居然正好也能A…怕了……还是希望以后出题人能认真一点,弄好标程和数据。

再说一下标程的错误解法:标程也是回溯时删东西,不过不是删边是删点……点和你无冤无仇,为什么删它?搜完一个点所有出边可不能保证这个点就没用了…就像我上面给的例子的点4。

 

代码:

  1 //#pragma comment(linker, "/STACK:102400000,102400000")
  2 #include<cstdio>
  3 #include<cmath>
  4 #include<iostream>
  5 #include<cstring>
  6 #include<algorithm>
  7 #include<cmath>
  8 #include<map>
  9 #include<set>
 10 #include<stack>
 11 #include<queue>
 12 using namespace std;
 13 #define ll long long
 14 #define usll unsigned ll
 15 #define mz(array) memset(array, 0, sizeof(array))
 16 #define minf(array) memset(array, 0x3f, sizeof(array))
 17 #define REP(i,n) for(i=0;i<(n);i++)
 18 #define FOR(i,x,n) for(i=(x);i<=(n);i++)
 19 #define RD(x) scanf("%d",&x)
 20 #define RD2(x,y) scanf("%d%d",&x,&y)
 21 #define RD3(x,y,z) scanf("%d%d%d",&x,&y,&z)
 22 #define WN(x) prllf("%d\n",x);
 23 #define RE  freopen("D.in","r",stdin)
 24 #define WE  freopen("1biao.out","w",stdout)
 25 #define mp make_pair
 26 #define pb push_back
 27 
 28 int ans;
 29 int r[511],co[511];
 30 int k,nr,nc;
 31 int sumr,sumc;
 32 const int maxn=1111;//点数
 33 const int maxm=555555;//边数
 34 const int inf=9;//MAXINT
 35 struct vnode {
 36     int v,next;
 37     int cap;
 38 };
 39 int cnt,head[maxn];
 40 int h[maxn],g[maxn],d[maxn];//g[i]为标号为i的结点个数,h[i]为i结点的标号,d[]当前弧优化,记录当前弧
 41 bool found;
 42 int n,m,st,ed;//n个点m条边
 43 int augc,flow;//augc为增广路容量,flow为最大流
 44 vnode e[maxm];
 45 
 46 inline void add(const int &x,const int &y,const int &z) {
 47     e[cnt].v=y;
 48     e[cnt].cap=z;
 49     e[cnt].next=head[x];
 50     head[x]=cnt;
 51     cnt++;
 52 
 53     e[cnt].v=x;
 54     e[cnt].cap=0;
 55     e[cnt].next=head[y];
 56     head[y]=cnt;
 57     cnt++;
 58 
 59 }
 60 
 61 
 62 bool walked[maxn];
 63 bool dfs(const int &x,const int &prex) {///深搜判环
 64     int biu=-1;
 65     walked[x]=true;
 66     for (int i=head[x]; i!=-1; i=e[i].next) {
 67         if(e[i].v==prex)continue;
 68         if (e[i].cap>0) {
 69             if(walked[e[i].v]) return true;
 70             if(dfs(e[i].v,x)) return true;
 71         }
 72         if(biu==-1) head[x]=e[i].next;///删边,因为这条边为0或者走了这条边却没发现环
 73         else e[biu].next=e[i].next;
 74         biu=i;
 75     }
 76     walked[x]=false;
 77     return false;
 78 }
 79 
 80 void aug(const int &m) {
 81     int mini,minh=n-1;
 82     int augco=augc;
 83     int &i=d[m];///当前弧优化
 84     if (m==ed) { //如果当前结点为汇点
 85         found=true;
 86         flow+=augc;    //增加流量
 87         return;
 88     }
 89     for (; i!=-1; i=e[i].next) { //寻找容许边
 90         //printf("m=%d,i=%d,e[i].v=%d,e[i].cap=%d,e[i].next=%d\n",m,i,e[i].v,e[i].cap,e[i].next);
 91         //getchar();
 92         if (e[i].cap && h[e[i].v]+1==h[m]) { //如果残留容量大于0,如果是容许边
 93             if (e[i].cap < augc)  augc=e[i].cap;//如果容许边流量小于当前增广路流量 则更新增广路流量
 94             //d[m]=i;    //把i定为当前弧
 95             aug(e[i].v);    //递归
 96             if (h[st]>=n) return; //GAP 如果源点距离标号大于n 则停止算法
 97             if (found) break;    //如果找到汇点 则退出寻找
 98             augc=augco;//没找到就还原当前的流
 99         }
100     }
101     if (!found) {      //重标号
102         for (int i=head[m]; i!=-1; i=e[i].next) //找那个标号,这里不能用d[m]开始,不然会蛋疼
103             if (e[i].cap && h[e[i].v]<minh) {
104                 minh=h[e[i].v];
105                 mini=i;
106             }
107         g[h[m]]--;                                 //GAP 距离为
108         if (!g[h[m]]) h[st]=n;                 //GAP
109         h[m]=minh+1;
110         d[m]=mini;
111         g[h[m]]++;                                 //GAP
112     } else {
113         //修改残量
114         e[i].cap-=augc;
115         e[i^1].cap+=augc;
116     }
117 }
118 
119 inline void farm() {
120     int i,j,x,y,z;
121     memset(head,-1,sizeof(head));
122     cnt=0;
123     n=nc+nr+2;
124     st=n-1;
125     ed=n;
126     for(i=1; i<=nc; i++)
127         add(st,i,co[i]);
128     for(i=1; i<=nr; i++)
129         add(nc+i,ed,r[i]);
130     for(i=1; i<=nc; i++)
131         for(j=1; j<=nr; j++)
132             add(i,j+nc,k);
133     memset(h,0,sizeof(h));
134     memset(g,0,sizeof(g));
135     g[0]=n;
136     flow=0;
137     for(i=1; i<=n; i++)
138         d[i]=head[i];//当前弧初始化
139     while(h[st]<n) {
140         augc=inf;//初始化增广路容量为正无穷大
141         found=false;
142         aug(st);//从源点开始找
143     }
144     if(flow!=sumr) {
145         ans=0;
146         return;
147     }
148     memset(walked,false,sizeof(walked));
149     for(i=1; i<=nr; i++) { ///因为建图的特性,有环的话环肯定包含1~nr中的点,也就是表示行的结点
150         if(dfs(i,-1)) {
151             ans=2;
152             return;
153         }
154     }
155     ans=1;
156     //printf("%d\n",flow);
157 }
158 
159 inline void read(int &a) {///读入优化
160     char ch;
161     bool flag = false;
162     a = 0;
163     while(!((((ch = getchar()) >= '0') && (ch <= '9')) || (ch == '-')));
164     if(ch != '-') {
165         a *= 10;
166         a += ch - '0';
167     } else {
168         flag = true;
169     }
170     while(((ch = getchar()) >= '0') && (ch <= '9')) {
171         a *= 10;
172         a += ch - '0';
173     }
174     if(flag) {
175         a = -a;
176     }
177 }
178 
179 int main() {
180     //RE;
181     //WE;
182     int i,j,cas=1,t;
183     scanf("%d",&t);
184     while(t--) {
185         scanf("%d%d",&nr,&nc);
186         k=9;
187         sumr=0;
188         sumc=0;
189         for(i=1; i<=nr; i++) {
190             //scanf("%d",&r[i]);
191             read(r[i]);
192             sumr+=r[i];
193         }
194         for(i=1; i<=nc; i++) {
195             //scanf("%d",&co[i]);
196             read(co[i]);
197             sumc+=co[i];
198         }
199         ans=0;
200         if(sumr==sumc)farm();
201         if(ans==0) printf("Case #%d: So naive!\n",cas++);
202         else if(ans!=1) {
203             printf("Case #%d: So young!\n",cas++);
204         } else {
205             printf("Case #%d: So simple!\n",cas++);
206         }
207     }
208     //cout<<"end";
209     return 0;
210 }
View Code

 

 

 

2014-10-16 Update

srm菊苣指出这个居然过不了4888,我试了一下,居然真的wa!

然后发现了一个小错误,if(e[i].v==prex)continue,continue之前没有修改biu。加上biu=i就能过了。

之前居然挂了错的代码这么久,见谅!我逗!

 

posted @ 2014-08-22 14:29  带鱼Yuiffy  阅读(1064)  评论(3编辑  收藏  举报