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: |
題意:有n行m列矩陣,給出各行的和、各列的和,矩陣元素需要為0~9,判斷無解、唯一解、多解。
題解:網路流+判環。
這題的做法就是hdu4888的做法,點上面的連結怒看如何建圖,我下面說這題的不同之處。網上很多題解都是錯的,標程也是錯的。
這題標程有錯,但沒有反例的資料,所以資料還是對的。但標程使用的錯誤方法使它執行時間巨短,導致這題時限開得少,直接拿超碉優化的網路流+簡單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就能過了。
之前居然掛了錯的程式碼這麼久,見諒!我逗!