1. 程式人生 > >hdu4975 A simple Gaussian elimination problem.(正確解法 最大流+刪邊判環)(Updated 2014-10-16)

hdu4975 A simple Gaussian elimination problem.(正確解法 最大流+刪邊判環)(Updated 2014-10-16)

這題標程是錯的,網上很多題解也是錯的。

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的做法,點上面的連結怒看如何建圖,我下面說這題的不同之處。網上很多題解都是錯的,標程也是錯的。

這題標程有錯,但沒有反例的資料,所以資料還是對的。但標程使用的錯誤方法使它執行時間巨短,導致這題時限開得少,直接拿超碉優化的網路流+簡單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就能過了。

之前居然掛了錯的程式碼這麼久,見諒!我逗!